Module:Probox and Module:Probox/sandbox: Difference between pages
Appearance
(Difference between pages)
Content deleted Content added
Update from master using Synchronizer #synchronizer |
Create sandbox version of Module:Probox |
||
Line 6: | Line 6: | ||
local p = {} |
local p = {} |
||
⚫ | |||
--function GetPageLang (frame) local pagelang = frame:callParserFunction{ name = '#translation:'} if pagelang == nil then pagelang = "/en" end return pagelang end |
|||
local function stringStarts(String,Start) |
|||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
-- adds additional template to the top of the page, above the infobox |
-- adds additional template to the top of the page, above the infobox |
||
local page_template |
local page_template |
||
-- local tmplt_args = {} |
-- local tmplt_args = {} |
||
local template_div = mw.html.create('div') |
local template_div = mw.html.create('div') |
||
for k,v in pairs(data.templates) do -- if there is a matching arg, and it has a table of template possibilities |
for k,v in pairs(data.templates) do -- if there is a matching arg, and it has a table of template possibilities |
||
if (args[k] and string.len(args[k]) > 0 and type(data.templates[k]) == "table") then --convert args to lowercase before checking them against cats |
if (args[k] and string.len(args[k]) > 0 and type(data.templates[k]) == "table") then --convert args to lowercase before checking them against cats |
||
Line 37: | Line 19: | ||
:wikitext(frame:expandTemplate{title=data.templates[k][tmplt_key], args={status = stringFirstCharToUpper(args[k])}}) --status is special casing. need to pass generic arg keys |
:wikitext(frame:expandTemplate{title=data.templates[k][tmplt_key], args={status = stringFirstCharToUpper(args[k])}}) --status is special casing. need to pass generic arg keys |
||
:done() |
:done() |
||
break |
break |
||
elseif (stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then |
elseif (stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then |
||
template_div |
template_div |
||
Line 48: | Line 30: | ||
-- if (type(tmplt_val) == "string" and stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then |
-- if (type(tmplt_val) == "string" and stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then |
||
end |
end |
||
end |
end |
||
end |
end |
||
page_template = tostring(template_div) |
page_template = tostring(template_div) |
||
return page_template |
return page_template |
||
end |
end |
||
function addCategories(data, args, open_roles) |
|||
-- will also look for numbered categories |
-- will also look for numbered categories |
||
local cat_list = {} |
local cat_list = {} |
||
Line 63: | Line 45: | ||
if data.categories.default then |
if data.categories.default then |
||
table.insert(cat_list, data.categories.default) |
table.insert(cat_list, data.categories.default) |
||
end |
end |
||
if data.categories.roles then |
if data.categories.roles then |
||
role_cat = data.categories.roles |
role_cat = data.categories.roles |
||
Line 83: | Line 65: | ||
end |
end |
||
end |
end |
||
end |
end |
||
⚫ | |||
local page_categories = "" |
|||
-- testing |
|||
local pagelang = mw.text.trim(frame:callParserFunction{name='#translation', args="1"}) |
|||
-- local pagelang = "/" .. mw.text.trim(frame:expandTemplate{title='CURRENTCONTENTLANGUAGE'}) |
|||
if #cat_list > 0 then |
|||
if pagelang == nil then |
|||
⚫ | |||
else |
|||
page_categories = "[[" .. tostring(table.concat(cat_list, pagelang .. "]] [[")) .. pagelang .. "]]" |
|||
end |
|||
⚫ | |||
if(args["status"] == "selected" or args["status"] == "SELECTED") then |
|||
maintainer = args["grantee"] or args["organization"] or args["creator"] or args["grantee1"] or args["grantee2"] |
|||
--if username is given as wikilink, extract clean username |
|||
maintainer = mw.ustring.match(maintainer, "%[%[[Uu]ser:(.+)|") or maintainer |
|||
page_categories = page_categories.. "[[Category:WMF grant reports by grantee|"..maintainer.."]]" |
|||
category_per_grantee = mw.title.new("Category:"..maintainer) |
|||
if category_per_grantee.exists then page_categories = page_categories.. "[[Category:"..maintainer.."]]" end |
|||
⚫ | |||
return page_categories |
return page_categories |
||
⚫ | |||
end |
|||
function makeTextField(field, field_div) |
|||
-- makes a formatted text field for output, based on parameter |
-- makes a formatted text field for output, based on parameter |
||
-- values or on default values provided for that field |
-- values or on default values provided for that field |
||
Line 123: | Line 79: | ||
:tag('span') |
:tag('span') |
||
:cssText(field.style2)--inconsistent use of styles 2/3 here |
:cssText(field.style2)--inconsistent use of styles 2/3 here |
||
:wikitext(field.title)--we probably aren't using this for most things now, after Heather's redesign |
:wikitext(field.title)--we probably aren't using this for most things now, after Heather's redesign |
||
:done() |
:done() |
||
:tag('span') |
:tag('span') |
||
:cssText(field.style3) |
:cssText(field.style3) |
||
:wikitext(field.values[1]) |
:wikitext(field.values[1]) |
||
:done() |
:done() |
||
elseif field.vtype2 == "title" then |
elseif field.vtype2 == "title" then |
||
field_div |
field_div |
||
Line 134: | Line 90: | ||
:cssText(field.style3) |
:cssText(field.style3) |
||
:wikitext(field.values[1]) |
:wikitext(field.values[1]) |
||
:done() |
:done() |
||
elseif field.vtype2 == "link" then |
elseif field.vtype2 == "link" then |
||
field_div |
field_div |
||
:tag('span') |
:tag('span') |
||
:cssText(field.style3) |
:cssText(field.style3) |
||
:wikitext("[[" .. field.values[1] .. "|<span style='" .. field.style2 .. "'>" .. field.title .."</span>]]") |
:wikitext("[[" .. field.values[1] .. "|<span style='" .. field.style2 .. "'>" .. field.title .."</span>]]") |
||
:done() |
:done() |
||
Line 144: | Line 100: | ||
field_div:done() |
field_div:done() |
||
return field_div |
return field_div |
||
end |
end |
||
function makeImageField(field, field_div) |
|||
-- makes a formatted image field for output, based on parameter values |
-- makes a formatted image field for output, based on parameter values |
||
-- provided in the calling template or its parent template, or on default |
-- provided in the calling template or its parent template, or on default |
||
Line 154: | Line 110: | ||
:tag('span') |
:tag('span') |
||
:cssText(field.style3) |
:cssText(field.style3) |
||
if field.vtype2 == "thumb" then |
if field.vtype2 == "thumb" then |
||
field_div |
field_div |
||
:wikitext("[[" .. field.values[1] .. "|right|" .. field.width .."]]") |
:wikitext("[[" .. field.values[1] .. "|right|" .. field.width .."]]") |
||
elseif field.vtype2 == "thumb2" then |
|||
field_div |
|||
:wikitext("[[" .. field.values[1] .. "|center|" .. field.width .."]]") |
|||
elseif field.vtype2 == "link" then |
elseif field.vtype2 == "link" then |
||
field_div |
field_div |
||
Line 167: | Line 120: | ||
field_div |
field_div |
||
:wikitext("[[" .. field.icon .. "|" .. field.alignment .. "|" .. field.width .."|link=" .. field.values[1] .. "]] " .. "[" .. field.values[1] .. " " .. field.title .. "]") |
:wikitext("[[" .. field.icon .. "|" .. field.alignment .. "|" .. field.width .."|link=" .. field.values[1] .. "]] " .. "[" .. field.values[1] .. " " .. field.title .. "]") |
||
end |
end |
||
elseif field.vtype2 == "ui_button" then |
elseif field.vtype2 == "ui_button" then |
||
field_div |
field_div |
||
:addClass(field.class) |
:addClass(field.class) |
||
:wikitext(field.title) |
:wikitext(field.title) |
||
:done() |
:done() |
||
end |
end |
||
field_div:done() |
field_div:done() |
||
return field_div |
return field_div |
||
end |
end |
||
function makeParticipantField(field, ftype) |
|||
local field_div = mw.html.create('div') |
local field_div = mw.html.create('div') |
||
local title_span |
local title_span |
||
Line 185: | Line 138: | ||
else |
else |
||
title_span = field.title |
title_span = field.title |
||
end |
end |
||
field_div |
field_div |
||
:cssText(field.style) |
:cssText(field.style) |
||
Line 206: | Line 159: | ||
else |
else |
||
v = "• " .. "[[User:" .. v .. "|" .. v .. "]]" |
v = "• " .. "[[User:" .. v .. "|" .. v .. "]]" |
||
end |
end |
||
field_div |
field_div |
||
:tag('span') |
:tag('span') |
||
:cssText(field.style3) |
:cssText(field.style3) |
||
:wikitext(v) |
:wikitext(v) |
||
-- :wikitext("• " .. "[[User:" .. v .. "|" .. v .. "]]") |
-- :wikitext("• " .. "[[User:" .. v .. "|" .. v .. "]]") |
||
:done() |
:done() |
||
i = i + 1 |
i = i + 1 |
||
end |
end |
||
end |
end |
||
field_div:allDone() |
field_div:allDone() |
||
return field_div |
return field_div |
||
end |
end |
||
function makeSectionDiv(sec_fields, sec_style) |
|||
local sec_div = mw.html.create('div'):cssText(sec_style) |
local sec_div = mw.html.create('div'):cssText(sec_style) |
||
sec_fields = TableTools.compressSparseArray(sec_fields) |
sec_fields = TableTools.compressSparseArray(sec_fields) |
||
for findex, sec_field in ipairs(sec_fields) do -- should put this at the end of the function, and just append the other stuff |
for findex, sec_field in ipairs(sec_fields) do -- should put this at the end of the function, and just append the other stuff |
||
sec_div:node(sec_field) |
sec_div:node(sec_field) |
||
end |
end |
||
return sec_div |
return sec_div |
||
end |
end |
||
function makeParticipantsSection(frame, args, data, filled_role_data, open_roles) |
|||
local filled_role_fields = {} |
local filled_role_fields = {} |
||
for role, val_table in pairs(filled_role_data) do |
for role, val_table in pairs(filled_role_data) do |
||
local field = data.fields[role] |
local field = data.fields[role] |
||
field.title = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.key}}) |
field.title = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.key}}) |
||
field.values = {} |
field.values = {} |
||
for val_num, val_text in ipairs(filled_role_data[role]) do |
for val_num, val_text in ipairs(filled_role_data[role]) do |
||
field.values[#field.values + 1] = val_text |
field.values[#field.values + 1] = val_text |
||
end |
end |
||
local filled_field_div = makeParticipantField(field, "filled") |
local filled_field_div = makeParticipantField(field, "filled") |
||
filled_role_fields[field.rank] = filled_field_div |
filled_role_fields[field.rank] = filled_field_div |
||
end |
end |
||
local sec_div = makeSectionDiv(filled_role_fields, data.styles.section["participants"]) |
local sec_div = makeSectionDiv(filled_role_fields, data.styles.section["participants"]) |
||
Line 245: | Line 198: | ||
-- if (args.portal == "Idealab" or args.portal == "Research") then -- beware, exceptions everywhere |
-- if (args.portal == "Idealab" or args.portal == "Research") then -- beware, exceptions everywhere |
||
sec_div:tag('span'):cssText("font-style:italic; color: #888888"):wikitext(mw.text.trim(frame:expandTemplate{title=args.translations, args={data.fields.more_participants.key}})):done() |
sec_div:tag('span'):cssText("font-style:italic; color: #888888"):wikitext(mw.text.trim(frame:expandTemplate{title=args.translations, args={data.fields.more_participants.key}})):done() |
||
-- elseif args.portal == "Patterns" then |
-- elseif args.portal == "Patterns" then |
||
-- sec_div:tag('span'):cssText("font-style:italic; color: #888888"):wikitext("a learning pattern for..."):done() |
-- sec_div:tag('span'):cssText("font-style:italic; color: #888888"):wikitext("a learning pattern for..."):done() |
||
-- else |
-- else |
||
for role, val in pairs(open_roles) do -- should make these ordered using compressSparseArray, as above |
for role, val in pairs(open_roles) do -- should make these ordered using compressSparseArray, as above |
||
local field = data.fields[role] |
local field = data.fields[role] |
||
Line 253: | Line 206: | ||
if field.icon then |
if field.icon then |
||
field.icon = field.icon_inactive |
field.icon = field.icon_inactive |
||
end |
end |
||
local open_field_div = makeParticipantField(field, "open") |
local open_field_div = makeParticipantField(field, "open") |
||
sec_div:node(open_field_div) |
sec_div:node(open_field_div) |
||
end |
end |
||
end |
end |
||
sec_div:allDone() |
sec_div:allDone() |
||
return sec_div |
return sec_div |
||
end |
end |
||
function makeSectionFields(args, field) |
|||
-- ui button is separate |
-- ui button is separate |
||
local field_div = mw.html.create('div'):cssText(field.style) --why declare this here? |
local field_div = mw.html.create('div'):cssText(field.style) --why declare this here? |
||
Line 268: | Line 221: | ||
if (field.isRequired == true or (args[field.arg] and string.len(args[field.arg]) > 0)) then --should move this up, may not just apply to images |
if (field.isRequired == true or (args[field.arg] and string.len(args[field.arg]) > 0)) then --should move this up, may not just apply to images |
||
field_div = makeImageField(field, field_div) |
field_div = makeImageField(field, field_div) |
||
end |
end |
||
elseif field.vtype == "text" then |
elseif field.vtype == "text" then |
||
field_div = makeTextField(field, field_div) |
field_div = makeTextField(field, field_div) |
||
else |
else |
||
end |
end |
||
return field_div -- make sure div is 'done' |
return field_div -- make sure div is 'done' |
||
end |
end |
||
function makeSection(frame, args, data, box_sec) |
|||
-- return a div for a section of the box including child divs |
-- return a div for a section of the box including child divs |
||
-- for each content field in that section |
-- for each content field in that section |
||
-- local sec_div = mw.html.create('div'):cssText(data.styles.section[box_sec]) |
-- local sec_div = mw.html.create('div'):cssText(data.styles.section[box_sec]) |
||
local sec_fields = {} |
local sec_fields = {} |
||
for k,v in pairs(data.fields) do |
for k,v in pairs(data.fields) do |
||
Line 290: | Line 243: | ||
if field.toLowerCase == true then -- special casing to make IEG status=SELECTED to display in lowercase |
if field.toLowerCase == true then -- special casing to make IEG status=SELECTED to display in lowercase |
||
field.values[1] = stringToLowerCase(field.values[1]) |
field.values[1] = stringToLowerCase(field.values[1]) |
||
end |
end |
||
local field_div = makeSectionFields(args, field) |
local field_div = makeSectionFields(args, field) |
||
sec_fields[field.rank] = field_div |
sec_fields[field.rank] = field_div |
||
elseif field.isRequired == true then |
elseif field.isRequired == true then |
||
if field.vtype == "text" then |
if field.vtype == "text" then |
||
Line 300: | Line 253: | ||
end |
end |
||
local field_div = makeSectionFields(args, field) |
local field_div = makeSectionFields(args, field) |
||
sec_fields[field.rank] = field_div |
sec_fields[field.rank] = field_div |
||
else |
else |
||
--don't make a section for this field |
--don't make a section for this field |
||
Line 308: | Line 261: | ||
local sec_div = makeSectionDiv(sec_fields, data.styles.section[box_sec]) |
local sec_div = makeSectionDiv(sec_fields, data.styles.section[box_sec]) |
||
return sec_div |
return sec_div |
||
end |
end |
||
function makeInfobox(frame, args, data, filled_role_data, open_roles) |
|||
-- builds the infobox. Some content sections are required, others |
-- builds the infobox. Some content sections are required, others |
||
-- are optional. Optional sections are defined in the stylesheet. |
-- are optional. Optional sections are defined in the stylesheet. |
||
Line 318: | Line 271: | ||
local sec_top = makeSection(frame, args, data, "above") |
local sec_top = makeSection(frame, args, data, "above") |
||
box:node(sec_top) |
box:node(sec_top) |
||
end |
end |
||
if data.sections.nav == true then |
if data.sections.nav == true then |
||
local sec_nav = makeSection(frame, args, data, "nav") |
local sec_nav = makeSection(frame, args, data, "nav") |
||
box:node(sec_nav) |
box:node(sec_nav) |
||
end |
end |
||
local sec_head = makeSection(frame, args, data, "head") |
local sec_head = makeSection(frame, args, data, "head") |
||
inner_box:node(sec_head) |
inner_box:node(sec_head) |
||
Line 333: | Line 286: | ||
if data.sections.cta == true then |
if data.sections.cta == true then |
||
local sec_cta = makeSection(frame, args, data, "cta") |
local sec_cta = makeSection(frame, args, data, "cta") |
||
sec_cta:addClass("noprint") -- no much use in print outs |
|||
inner_box:node(sec_cta) |
inner_box:node(sec_cta) |
||
inner_box:tag('div'):cssText("clear:both |
inner_box:tag('div'):cssText("clear:both"):done() --clears buttons in the cta sections |
||
end |
end |
||
inner_box:allDone() |
inner_box:allDone() |
||
box:node(inner_box) |
box:node(inner_box) |
||
Line 342: | Line 294: | ||
local sec_bottom = makeSection(frame, args, data, "below") |
local sec_bottom = makeSection(frame, args, data, "below") |
||
box:node(sec_bottom) |
box:node(sec_bottom) |
||
end |
end |
||
box:allDone() |
box:allDone() |
||
return box |
return box |
||
end |
end |
||
function orderStringtoNumber(array, val, num) |
|||
if num > table.getn(array) then |
if num > table.getn(array) then |
||
array[#array+1] = val |
array[#array+1] = val |
||
else |
else |
||
table.insert(array, num, val) |
table.insert(array, num, val) |
||
end |
end |
||
return array |
return array |
||
end |
end |
||
function isJoinable(args, data) |
|||
if args.more_participants == "NO" then |
if args.more_participants == "NO" then |
||
data.fields.join = nil |
data.fields.join = nil |
||
data.fields.endorse.style = "display:inline; float:right;" |
|||
⚫ | |||
data.fields.endorse.style = "display:inline; float:right;" |
|||
end |
|||
end |
|||
return data |
return data |
||
end |
end |
||
function deepCopyTable(data) |
|||
-- the deep copy is a workaround step to avoid the restrictions placed on |
-- the deep copy is a workaround step to avoid the restrictions placed on |
||
-- tables imported through loadData |
-- tables imported through loadData |
||
Line 380: | Line 330: | ||
end |
end |
||
function getPortalData(args) |
|||
-- loads the relevant stylesheet, if a sub-template was called with a portal |
-- loads the relevant stylesheet, if a sub-template was called with a portal |
||
-- argument and a stylesheet exists with the same name. For example, calling |
-- argument and a stylesheet exists with the same name. For example, calling |
||
-- {{#invoke:Probox|main|portal=Idealab}} would load the Module:Probox/Idealab |
-- {{#invoke:Probox/Idealab|main|portal=Idealab}} would load the |
||
-- Module:Probox/Idealab stylesheet |
|||
-- stylesheet. |
|||
local data_readOnly = {} |
local data_readOnly = {} |
||
local data_writable = {} |
local data_writable = {} |
||
Line 391: | Line 341: | ||
else |
else |
||
data_readOnly = mw.loadData("Module:Probox/Default") |
data_readOnly = mw.loadData("Module:Probox/Default") |
||
end |
end |
||
-- data_writable = TableTools.shallowClone(data_readOnly) |
-- data_writable = TableTools.shallowClone(data_readOnly) |
||
data_writable = deepCopyTable(data_readOnly) |
data_writable = deepCopyTable(data_readOnly) |
||
Line 397: | Line 347: | ||
end |
end |
||
function string.starts(String,Start) |
|||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
function TCTlookup(args) |
|||
local tct_path = tostring(args.translations) |
local tct_path = tostring(args.translations) |
||
--mw.log(mw.title.getCurrentTitle().subpageText) |
--mw.log(mw.title.getCurrentTitle().subpageText) |
||
Line 410: | Line 376: | ||
-- mw.log(tct_path) |
-- mw.log(tct_path) |
||
return tct_path |
return tct_path |
||
end |
end |
||
function getRoleArgs(args, available_roles) |
|||
-- returns: |
-- returns: |
||
-- 1) a table of ordered values for valid role params, |
-- 1) a table of ordered values for valid role params, |
||
Line 422: | Line 388: | ||
if available_roles.default then -- some boxes have default role to join |
if available_roles.default then -- some boxes have default role to join |
||
open_roles[available_roles.default] = true |
open_roles[available_roles.default] = true |
||
end |
end |
||
for rd_key, rd_val in pairs(available_roles) do |
for rd_key, rd_val in pairs(available_roles) do |
||
for a_key, a_val in pairs(args) do |
for a_key, a_val in pairs(args) do |
||
if |
if string.starts(a_key, rd_key) then |
||
if string.len(a_val) == 0 then |
if string.len(a_val) == 0 then |
||
open_roles[rd_key] = true |
open_roles[rd_key] = true |
||
Line 434: | Line 400: | ||
filled_role_data[rd_key] = orderStringtoNumber(filled_role_data[rd_key], a_val, arg_num) |
filled_role_data[rd_key] = orderStringtoNumber(filled_role_data[rd_key], a_val, arg_num) |
||
else |
else |
||
table.insert(filled_role_data[rd_key], 1, a_val) |
table.insert(filled_role_data[rd_key], 1, a_val) |
||
end |
end |
||
end |
end |
||
end |
end |
||
end |
end |
||
end |
end |
||
return filled_role_data, open_roles |
return filled_role_data, open_roles |
||
end |
end |
||
function p.main(frame) |
function p.main(frame) |
||
Line 452: | Line 418: | ||
-- mw.log(args.translations) |
-- mw.log(args.translations) |
||
-- if the TCT content index is under translation, check for translations in the subpage language |
-- if the TCT content index is under translation, check for translations in the subpage language |
||
if mw.title.new("Template:" .. args.translations .. "/en").exists then |
if mw.title.new("Template:" .. args.translations .. "/en").exists then |
||
args.translations = TCTlookup(args) |
args.translations = TCTlookup(args) |
||
end |
end |
||
if data.sections.cta == true then |
if data.sections.cta == true then |
||
args.talk = tostring(mw.title.getCurrentTitle().talkPageTitle) -- expensive |
args.talk = tostring(mw.title.getCurrentTitle().talkPageTitle) -- expensive |
||
end |
end |
||
local filled_role_data, open_roles = getRoleArgs(args, data.roles) |
local filled_role_data, open_roles = getRoleArgs(args, data.roles) |
||
local box = makeInfobox(frame, args, data, filled_role_data, open_roles) |
local box = makeInfobox(frame, args, data, filled_role_data, open_roles) |
||
local infobox = tostring(box) |
local infobox = tostring(box) |
||
-- only add cats if not in Template or User |
-- only add cats if not in Template or User ns |
||
if (data.categories and (mw.title.getCurrentTitle().nsText ~= "Template" and mw.title.getCurrentTitle().nsText ~= "User" and mw.title.getCurrentTitle().nsText ~= "Meta") and not args.noindex) then |
if (data.categories and (mw.title.getCurrentTitle().nsText ~= "Template" and mw.title.getCurrentTitle().nsText ~= "User" and mw.title.getCurrentTitle().nsText ~= "Meta") and not args.noindex) then |
||
-- FIXME specify namespace in config, so that categories only appear if template is translcuded in that namespace |
-- FIXME specify namespace in config, so that categories only appear if template is translcuded in that namespace |
||
page_categories = addCategories(data, args, open_roles) |
|||
infobox = infobox .. page_categories |
infobox = infobox .. page_categories |
||
end |
end |
||
if data.templates then |
if data.templates then |
||
local top_template = addTemplates(frame, data, args) |
local top_template = addTemplates(frame, data, args) |
||
infobox = top_template .. infobox |
infobox = top_template .. infobox |
||
end |
end |
||
return infobox |
return infobox |
||
end |
end |