편집을 취소할 수 있습니다. 이 편집을 되돌리려면 아래의 바뀐 내용을 확인한 후 게시해주세요.
최신판 | 당신의 편집 | ||
1번째 줄: | 1번째 줄: | ||
-- This is a meta-module for producing message box templates, including | -- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}. | ||
-- | -- Require necessary modules. | ||
require('Module: | local htmlBuilder = require('Module:HtmlBuilder') | ||
local | local categoryHandler = require('Module:Category handler').main | ||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
10번째 줄: | 9번째 줄: | ||
local lang = mw.language.getContentLanguage() | local lang = mw.language.getContentLanguage() | ||
-- | -- Set aliases for often-used functions to reduce table lookups. | ||
local | local format = mw.ustring.format | ||
local | local tinsert = table.insert | ||
local | local tconcat = table.concat | ||
local p = {} | |||
local function getTitleObject( | 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 | end | ||
local function | local function presentButBlank(s) | ||
if type(s) ~= 'string' then return end | |||
if s and not mw.ustring.find(s, '%S') then | |||
return true | |||
else | |||
return false | |||
end | |||
end | end | ||
local function | local function formatCategory(cat, date, all) | ||
local ret = {} | |||
cat = type(cat) == 'string' and cat | |||
date = type(date) == 'string' and date | |||
all = type(all) == 'string' and all | |||
local preposition = 'from' | |||
if cat and date then | |||
local catTitle = format('Category:%s %s %s', cat, preposition, date) | |||
tinsert(ret, format('[[%s]]', catTitle)) | |||
catTitle = getTitleObject(catTitle) | |||
if not catTitle or not catTitle.exists then | |||
tinsert(ret, '[[Category:Articles with invalid date parameter in template]]') | |||
end | |||
elseif cat and not date then | |||
tinsert(ret, format('[[Category:%s]]', cat)) | |||
end | |||
if all then | |||
tinsert(ret, format('[[Category:%s]]', all)) | |||
end | |||
return tconcat(ret) | |||
end | end | ||
local function union(t1, t2) | |||
-- Returns the union of two arrays. | |||
local vals = {} | |||
for i, v in ipairs(t1) do | |||
local | vals[v] = true | ||
end | |||
for i, v in ipairs(t2) do | |||
function | vals[v] = true | ||
end | |||
local ret = {} | |||
for k in pairs(vals) do | |||
tinsert(ret, k) | |||
end | |||
table.sort(ret) | |||
return ret | |||
end | end | ||
function | 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 | end | ||
function | local function getNamespaceId(ns) | ||
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 | end | ||
function | local function getMboxType(nsid) | ||
-- Gets the mbox type from a namespace number. | |||
if nsid == 0 then | |||
return 'ambox' -- main namespace | |||
elseif nsid == 6 then | |||
return 'imbox' -- file namespace | |||
elseif nsid == 14 then | |||
return 'cmbox' -- category 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 | ||
function | function p.build(boxType, args) | ||
if type(args) ~= 'table' then | |||
error(format('invalid "args" parameter type; expected type "table", got type "%s"', type(args)), 2) | |||
end | |||
-- Get the title object and the namespace. | |||
local title = getTitleObject(args.page) or mw.title.getCurrentTitle() | |||
local nsid = getNamespaceId(args.demospace) or title.namespace | |||
-- Get the box config data from the data page. | |||
if boxType == 'mbox' then | |||
boxType = getMboxType(nsid) | |||
end | |||
local dataTables = mw.loadData('Module:Message box/data') | |||
local data = dataTables[boxType] | |||
if not data then | |||
local boxTypes = {} | |||
for k, v in pairs(dataTables) do | |||
tinsert(boxTypes, format('"%s"', k)) | |||
end | |||
tinsert(boxTypes, '"mbox"') | |||
error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2) | |||
end | |||
end | |||
------------------------ Process config data ---------------------------- | |||
-- Type data. | |||
local typeData = data.types[args.type] | |||
local invalidType = args.type and not typeData and true or false | |||
typeData = typeData or data.types[data.default] | |||
-- Process data for collapsible text fields | |||
local name, issue, talk, fix, date, info | |||
if data.useCollapsibleTextFields then | |||
name = args.name | |||
local nameTitle = getTitleObject(name) | |||
local isTemplatePage = nameTitle and title.prefixedText == ('Template:' .. nameTitle.text) and true or false | |||
local sect = args.sect | |||
if presentButBlank(sect) then | |||
sect = format('This %s ', data.sectionDefault or 'page') | |||
elseif type(sect) == 'string' then | |||
sect = 'This ' .. sect .. ' ' | |||
end | |||
issue = (sect or '') .. (args.issue or '') .. ' ' .. (args.text or '') | |||
talk = args.talk | |||
if presentButBlank(talk) and isTemplatePage then | |||
talk = '#' | |||
end | end | ||
fix = args.fix | |||
date = args.date | |||
if presentButBlank(date) and isTemplatePage then | |||
date = lang:formatDate('F Y') | |||
end | |||
info = args.info | |||
end | |||
-- Process the talk link, if present. | |||
if talk then | |||
-- See if the talk link exists and is for a talk or a content namespace. | |||
local talkTitle = type(talk) == 'string' and getTitleObject(talk) | |||
if not talkTitle or not talkTitle.isTalkPage then | |||
-- If we couldn't process the talk page link, get the talk page of the current page. | |||
local success | |||
success, talkTitle = pcall(title.talkPageTitle, title) | |||
if not success then | |||
talkTitle = nil | |||
end | |||
end | |||
if talkTitle and talkTitle.exists then | |||
local talkText = ' Relevant discussion may be found on' | |||
if talkTitle.isTalkPage then | |||
talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText) | |||
else | |||
talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk) | |||
end | |||
talk = talkText | |||
end | |||
end | |||
-- Find whether we are using a small message box and process our data accordingly. | |||
local isSmall = data.allowSmall and (args.small == 'yes' or args.small == true) and true or false | |||
local smallClass, image, imageRight, text, imageSize | |||
if isSmall then | |||
smallClass = data.smallClass or 'mbox-small' | |||
image = args.smallimage or args.image | |||
imageRight = args.smallimageright or args.imageright | |||
if data.useCollapsibleTextFields then | |||
text = args.smalltext or issue | |||
else | |||
text = args.smalltext or args.text | |||
end | |||
imageSize = data.imageSmallSize or '30x30px' | |||
else | |||
image = args.image | |||
imageRight = args.imageright | |||
imageSize = '40x40px' | |||
text = args.text | |||
end | |||
-- Process mainspace categories. | |||
local mainCats = {} | |||
local origCategoryNums -- origCategoryNums might be used in computing the template error category. | |||
if data.allowMainspaceCategories then | |||
-- Categories for the main namespace. | |||
local origCatNums = getArgNums(args, 'cat') | |||
local origCategoryNums = getArgNums(args, 'category') | |||
local catNums = union(origCatNums, origCategoryNums) | |||
for _, num in ipairs(catNums) do | |||
local cat = args['cat' .. tostring(num)] or args['category' .. tostring(num)] | |||
local all = args['all' .. tostring(num)] | |||
tinsert(mainCats, formatCategory(cat, args.date, all)) | |||
end | |||
end | |||
-- Process template namespace categories | |||
local templateCats = {} | |||
if data.templateCategory and not title.isSubpage and not yesno(args.nocat) then | |||
tinsert(templateCats, format('[[Category:%s]]', data.templateCategory)) | |||
end | |||
-- Add an error category for the template namespace if appropriate. | |||
if data.templateErrorCategory then | |||
local catName = data.templateErrorCategory | |||
local templateCat | |||
if not name and not title.isSubpage then | |||
templateCat = format('[[Category:%s]]', catName) | |||
elseif type(name) == 'string' and title.prefixedText == ('Template:' .. name) then | |||
local paramsToCheck = data.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 = format('[[Category:%s|%d]]', catName, count) | |||
end | |||
if origCategoryNums and #origCategoryNums > 0 then | |||
templateCat = format('[[Category:%s|C]]', catName) | |||
end | |||
end | |||
tinsert(templateCats, templatecat) | |||
end | |||
-- Categories for all namespaces. | |||
local allCats = {} | |||
if invalidType then | |||
local catsort = (nsid == 0 and 'Main:' or '') .. title.prefixedText | |||
tinsert(allCats, format('[[Category:Wikipedia message box parameter needs fixing|%s]]', catsort)) | |||
end | |||
------------------------ Build the box ---------------------------- | |||
local root = htmlBuilder.create() | |||
-- Do the subst check. | |||
if data.substCheck and args.subst == 'SUBST' then | |||
if type(name) == 'string' then | |||
root | |||
.tag('b') | |||
.addClass('error') | |||
.wikitext(format( | |||
'Template <code>%s%s%s</code> has been incorrectly substituted.', | |||
mw.text.nowiki('{{'), name, mw.text.nowiki('}}') | |||
)) | |||
end | |||
tinsert(allCats, '[[Category:Pages with incorrectly substituted templates]]') | |||
end | |||
-- Create the box table. | |||
local box = root.tag('table') | |||
box | |||
.attr('id', args.id) | |||
for i, class in ipairs(data.classes) do | |||
box | |||
.addClass(class) | |||
end | |||
box | |||
.addClass(isSmall and smallClass) | |||
.addClass(data.classPlainlinksYesno and yesno(args.plainlinks or true) and 'plainlinks') | |||
.addClass(typeData.class) | |||
.addClass(args.class) | |||
.cssText(args.style) | |||
.attr('role', 'presentation') | |||
-- Add the left-hand image. | |||
local row = box.tag('tr') | |||
local imageCheckBlank = data.imageCheckBlank | |||
if image ~= 'none' and not imageCheckBlank or image ~= 'none' and imageCheckBlank and image ~= 'blank' then | |||
local imageLeftCell = row.tag('td').addClass('mbox-image') | |||
if not isSmall and data.imageCellDiv then | |||
imageLeftCell = imageLeftCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageLeftCell so that the image is inside it. | |||
end | |||
imageLeftCell | |||
.wikitext(image or format('[[File:%s|%s|link=|alt=]]', typeData.image, imageSize)) | |||
elseif data.imageEmptyCell then | |||
row.tag('td') | |||
.addClass('mbox-empty-cell') -- No image. Cell with some width or padding necessary for text cell to have 100% width. | |||
.cssText(data.imageEmptyCellStyle and 'border:none;padding:0px;width:1px') | |||
end | |||
-- Add the text. | |||
local textCell = row.tag('td').addClass('mbox-text') | |||
if data.useCollapsibleTextFields then | |||
textCell | |||
.cssText(args.textstyle) | |||
local textCellSpan = textCell.tag('span') | |||
textCellSpan | |||
.addClass('mbox-text-span') | |||
.wikitext(issue) | |||
if not isSmall then | |||
textCellSpan | |||
.tag('span') | |||
.addClass('hide-when-compact') | |||
.wikitext(talk) | |||
.wikitext(' ') | |||
.wikitext(fix) | |||
.done() | |||
end | |||
textCellSpan | |||
.wikitext(date and format(" <small>''(%s)''</small>", date)) | |||
if not isSmall then | |||
textCellSpan | |||
.tag('span') | |||
.addClass('hide-when-compact') | |||
.wikitext(info and ' ' .. info) | |||
end | |||
else | |||
textCell | |||
.cssText(args.textstyle) | |||
.wikitext(text) | |||
end | |||
-- Add the right-hand image. | |||
if imageRight and not (data.imageRightNone and imageRight == 'none') then | |||
local imageRightCell = row.tag('td').addClass('mbox-imageright') | |||
if not isSmall and data.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(imageRight) | |||
end | |||
-- Add the below row. | |||
end | if data.below and args.below then | ||
box.tag('tr') | |||
.tag('td') | |||
.attr('colspan', args.imageright and '3' or '2') | |||
.addClass('mbox-text') | |||
.cssText(args.textstyle) | |||
.wikitext(args.below) | |||
end | |||
------------------------ | ------------------------ Error messages and categories ---------------------------- | ||
-- Add error message for invalid type parameters. | |||
if invalidType then | |||
root | |||
.tag('div') | |||
.addClass('error') | |||
.css('text-align', 'center') | |||
.wikitext(format('This message box is using an invalid type parameter (<code>type=%s</code>) and needs fixing.', args.type or '')) | |||
end | |||
-- Add categories using categoryHandler. | |||
root | |||
.wikitext(categoryHandler{ | |||
main = tconcat(mainCats), | |||
template = tconcat(templateCats), | |||
all = tconcat(allCats) | |||
}) | |||
return tostring(root) | |||
end | end | ||
function | local function makeWrapper(boxType) | ||
return function (frame) | |||
-- If called via #invoke, use the args passed into the invoking | |||
-- template, or the args passed to #invoke if any exist. Otherwise | |||
-- assume args are being passed directly in from the debug console | |||
-- or from another Lua module. | |||
local origArgs | |||
if frame == mw.getCurrentFrame() then | |||
origArgs = frame:getParent().args | |||
for k, v in pairs(frame.args) do | |||
origArgs = frame.args | |||
break | |||
end | |||
else | |||
origArgs = frame | |||
end | |||
-- Trim whitespace and remove blank arguments. | |||
local args = {} | |||
for k, v in pairs(origArgs) do | |||
if type(v) == 'string' then | |||
v = mw.text.trim(v) | |||
end | |||
if v ~= '' or k == 'talk' or k == 'sect' or k == 'date' then | |||
args[k] = v | |||
end | |||
end | |||
return p.build(boxType, args) | |||
end | |||
end | end | ||
p.mbox = makeWrapper('mbox') | |||
p.ambox = makeWrapper('ambox') | |||
p.cmbox = makeWrapper('cmbox') | |||
p.fmbox = makeWrapper('fmbox') | |||
p.imbox = makeWrapper('imbox') | |||
p.ombox = makeWrapper('ombox') | |||
p.tmbox = makeWrapper('tmbox') | |||
return | return p |