https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AProbox%2FsandboxModule:Probox/sandbox - Revision history2025-06-02T10:19:00ZRevision history for this page on the wikiMediaWiki 1.45.0-wmf.3https://en.wikipedia.org/w/index.php?title=Module:Probox/sandbox&diff=750605187&oldid=prevPrahlad balaji: Create sandbox version of Module:Probox2016-11-20T20:06:08Z<p>Create sandbox version of <a href="/wiki/Module:Probox" title="Module:Probox">Module:Probox</a></p>
<p><b>New page</b></p><div>--<br />
-- This module implements {{Probox}}<br />
--<br />
local getArgs = require('Module:Arguments').getArgs<br />
local TableTools = require('Module:TableTools')<br />
local p = {}<br />
<br />
function addTemplates(frame, data, args)<br />
-- adds additional template to the top of the page, above the infobox<br />
local page_template<br />
-- local tmplt_args = {}<br />
local template_div = mw.html.create('div') <br />
for k,v in pairs(data.templates) do -- if there is a matching arg, and it has a table of template possibilities<br />
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<br />
for tmplt_key, tmplt_val in pairs(data.templates[k]) do<br />
if data.templates.passArg == true then<br />
template_div<br />
:cssText("")<br />
:wikitext(frame:expandTemplate{title=data.templates[k][tmplt_key], args={status = stringFirstCharToUpper(args[k])}}) --status is special casing. need to pass generic arg keys<br />
:done()<br />
break <br />
elseif (stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then <br />
template_div<br />
:cssText("margin-bottom:1em;")<br />
:wikitext(frame:expandTemplate{title=tmplt_val, args={}})<br />
:done()<br />
end<br />
--convert args to lowercase and subs spaces for underscores<br />
--make sure specified template exists<br />
-- if (type(tmplt_val) == "string" and stringToLowerCase(args[k]) == tmplt_key and mw.title.new("Template:" .. tmplt_val).exists) then <br />
<br />
end <br />
end<br />
end <br />
page_template = tostring(template_div) <br />
return page_template<br />
end<br />
<br />
function addCategories(data, args, open_roles)<br />
-- will also look for numbered categories<br />
local cat_list = {}<br />
local base_cat = data.categories.base<br />
-- table.insert(cat_list, base_cat) --always need a base category<br />
-- -- adding role categories<br />
if data.categories.default then<br />
table.insert(cat_list, data.categories.default)<br />
end <br />
if data.categories.roles then<br />
role_cat = data.categories.roles<br />
for k,v in pairs(open_roles) do<br />
table.insert(cat_list, base_cat .. k:gsub("^%l", string.upper) .. role_cat)<br />
end<br />
end<br />
for k,v in pairs(data.categories) do -- if there is a matching arg, and it has a table of category possibilities<br />
if (args[k] and string.len(args[k]) > 0) then --convert args to lowercase before checking them against cats<br />
if type(data.categories[k]) == "table" then<br />
for cat_key, cat_val in pairs(data.categories[k]) do<br />
if stringSpacesToUnderscores(stringToLowerCase(args[k])) == cat_key then --convert args to lowercase and subs spaces for underscores before checking them against cats<br />
table.insert(cat_list, base_cat .. cat_val)<br />
break<br />
end<br />
end<br />
elseif type(data.categories[k]) == "string" then --concat the value of the cat field with the default cat directly<br />
table.insert(cat_list, base_cat .. data.categories[k])<br />
end<br />
end<br />
end <br />
local page_categories = " [[" .. tostring(table.concat(cat_list, "]] [[")) .. "]]"<br />
return page_categories<br />
end <br />
<br />
function makeTextField(field, field_div)<br />
-- makes a formatted text field for output, based on parameter<br />
-- values or on default values provided for that field <br />
-- type in the stylesheet<br />
field_div:cssText(field.style)<br />
if field.vtype2 == "body" then<br />
field_div<br />
:tag('span')<br />
:cssText(field.style2)--inconsistent use of styles 2/3 here<br />
:wikitext(field.title)--we probably aren't using this for most things now, after Heather's redesign <br />
:done()<br />
:tag('span')<br />
:cssText(field.style3)<br />
:wikitext(field.values[1])<br />
:done() <br />
elseif field.vtype2 == "title" then<br />
field_div<br />
:tag('span')<br />
:cssText(field.style3)<br />
:wikitext(field.values[1])<br />
:done() <br />
elseif field.vtype2 == "link" then<br />
field_div<br />
:tag('span')<br />
:cssText(field.style3) <br />
:wikitext("[[" .. field.values[1] .. "|<span style='" .. field.style2 .. "'>" .. field.title .."</span>]]")<br />
:done()<br />
end<br />
field_div:done()<br />
return field_div<br />
end <br />
<br />
function makeImageField(field, field_div)<br />
-- makes a formatted image field for output, based on parameter values<br />
-- provided in the calling template or its parent template, or on default<br />
-- values provided in the stylesheet<br />
field_div:cssText(field.style)<br />
field_div<br />
:tag('span')<br />
:cssText(field.style3)<br />
if field.vtype2 == "thumb" then <br />
field_div <br />
:wikitext("[[" .. field.values[1] .. "|right|" .. field.width .."]]")<br />
elseif field.vtype2 == "link" then<br />
field_div<br />
:wikitext("[[" .. field.values[1] .. "|" .. field.alignment .. "|" .. field.width .."|link=" .. field.link .. "]]")<br />
elseif field.vtype2 == "badge" then<br />
if mw.ustring.find( field.values[1], "http", 1, true ) then<br />
field_div<br />
:wikitext("[[" .. field.icon .. "|" .. field.alignment .. "|" .. field.width .."|link=" .. field.values[1] .. "]] " .. "[" .. field.values[1] .. " " .. field.title .. "]")<br />
end <br />
elseif field.vtype2 == "ui_button" then<br />
field_div<br />
:addClass(field.class) <br />
:wikitext(field.title)<br />
:done() <br />
end <br />
field_div:done() <br />
return field_div <br />
end <br />
<br />
function makeParticipantField(field, ftype)<br />
local field_div = mw.html.create('div')<br />
local title_span<br />
if field.icon then <br />
title_span = "[[" .. field.icon .. "|left" .. "|18px]] " .. field.title<br />
else<br />
title_span = field.title<br />
end <br />
field_div<br />
:cssText(field.style)<br />
:tag('span')<br />
:cssText(field.style2)<br />
:wikitext(title_span)<br />
:done()<br />
if ftype == "filled" then<br />
local i = 1<br />
for k,v in ipairs(field.values) do<br />
if (i > 1 and field.icon) then --only insert extra padding if has icon<br />
field.style3 = "padding-left:25px; display:block"<br />
end<br />
if field.vtype2 then --ideally all configs should at least have this field for participants. FIXME<br />
if field.vtype2 == "username" then<br />
v = "• " .. "[[User:" .. v .. "|" .. v .. "]]"<br />
elseif field.vtype2 == "email" then<br />
v = "• " .. v<br />
end<br />
else<br />
v = "• " .. "[[User:" .. v .. "|" .. v .. "]]"<br />
end <br />
field_div<br />
:tag('span')<br />
:cssText(field.style3)<br />
:wikitext(v) <br />
-- :wikitext("• " .. "[[User:" .. v .. "|" .. v .. "]]")<br />
:done() <br />
i = i + 1<br />
end <br />
end <br />
field_div:allDone()<br />
return field_div<br />
end<br />
<br />
function makeSectionDiv(sec_fields, sec_style)<br />
local sec_div = mw.html.create('div'):cssText(sec_style) <br />
sec_fields = TableTools.compressSparseArray(sec_fields)<br />
for findex, sec_field in ipairs(sec_fields) do -- should put this at the end of the function, and just append the other stuff<br />
sec_div:node(sec_field)<br />
end <br />
return sec_div<br />
end<br />
<br />
function makeParticipantsSection(frame, args, data, filled_role_data, open_roles)<br />
local filled_role_fields = {}<br />
for role, val_table in pairs(filled_role_data) do<br />
local field = data.fields[role]<br />
field.title = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.key}})<br />
field.values = {} <br />
for val_num, val_text in ipairs(filled_role_data[role]) do<br />
field.values[#field.values + 1] = val_text <br />
end<br />
local filled_field_div = makeParticipantField(field, "filled") <br />
filled_role_fields[field.rank] = filled_field_div <br />
end<br />
local sec_div = makeSectionDiv(filled_role_fields, data.styles.section["participants"]) <br />
if (data.fields.more_participants and args.more_participants and stringToLowerCase(args.more_participants)) == "yes" then -- really need this here?<br />
-- if (args.portal == "Idealab" or args.portal == "Research") then -- beware, exceptions everywhere<br />
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()<br />
-- elseif args.portal == "Patterns" then <br />
-- sec_div:tag('span'):cssText("font-style:italic; color: #888888"):wikitext("a learning pattern for..."):done()<br />
-- else <br />
for role, val in pairs(open_roles) do -- should make these ordered using compressSparseArray, as above<br />
local field = data.fields[role]<br />
field.title = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.key}})<br />
if field.icon then<br />
field.icon = field.icon_inactive<br />
end <br />
local open_field_div = makeParticipantField(field, "open") <br />
sec_div:node(open_field_div)<br />
end <br />
end <br />
sec_div:allDone()<br />
return sec_div<br />
end <br />
<br />
function makeSectionFields(args, field)<br />
-- ui button is separate<br />
local field_div = mw.html.create('div'):cssText(field.style) --why declare this here?<br />
if field.vtype == "image" then<br />
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<br />
field_div = makeImageField(field, field_div)<br />
end <br />
elseif field.vtype == "text" then<br />
field_div = makeTextField(field, field_div) <br />
else<br />
end <br />
return field_div -- make sure div is 'done'<br />
end<br />
<br />
function makeSection(frame, args, data, box_sec) <br />
-- return a div for a section of the box including child divs<br />
-- for each content field in that section<br />
-- local sec_div = mw.html.create('div'):cssText(data.styles.section[box_sec]) <br />
local sec_fields = {}<br />
for k,v in pairs(data.fields) do<br />
if data.fields[k].section == box_sec then<br />
local field = data.fields[k]<br />
field.title = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.key}})<br />
field.values = {}<br />
if (args[k] and string.len(args[k]) > 0) then<br />
field.values[1] = args[k] --does not accept numbered args<br />
if field.toLowerCase == true then -- special casing to make IEG status=SELECTED to display in lowercase<br />
field.values[1] = stringToLowerCase(field.values[1])<br />
end <br />
local field_div = makeSectionFields(args, field)<br />
sec_fields[field.rank] = field_div <br />
elseif field.isRequired == true then<br />
if field.vtype == "text" then<br />
field.values[1] = mw.text.trim(frame:expandTemplate{title=args.translations, args={field.default}})<br />
else<br />
field.values[1] = field.default<br />
end<br />
local field_div = makeSectionFields(args, field)<br />
sec_fields[field.rank] = field_div <br />
else<br />
--don't make a section for this field<br />
end<br />
end<br />
end<br />
local sec_div = makeSectionDiv(sec_fields, data.styles.section[box_sec]) <br />
return sec_div<br />
end <br />
<br />
function makeInfobox(frame, args, data, filled_role_data, open_roles)<br />
-- builds the infobox. Some content sections are required, others <br />
-- are optional. Optional sections are defined in the stylesheet.<br />
local box = mw.html.create('div'):cssText(data.styles.box.outer)<br />
local inner_box = mw.html.create('div'):cssText(data.styles.box.inner)<br />
if data.sections.above == true then<br />
local sec_top = makeSection(frame, args, data, "above")<br />
box:node(sec_top)<br />
end <br />
if data.sections.nav == true then<br />
local sec_nav = makeSection(frame, args, data, "nav")<br />
box:node(sec_nav)<br />
end <br />
local sec_head = makeSection(frame, args, data, "head")<br />
inner_box:node(sec_head)<br />
local sec_main = makeSection(frame, args, data, "main")<br />
inner_box:node(sec_main)<br />
if data.sections.participants == true then<br />
local sec_participants = makeParticipantsSection(frame, args, data, filled_role_data, open_roles)<br />
inner_box:node(sec_participants)<br />
end<br />
if data.sections.cta == true then<br />
local sec_cta = makeSection(frame, args, data, "cta")<br />
inner_box:node(sec_cta)<br />
inner_box:tag('div'):cssText("clear:both"):done() --clears buttons in the cta sections<br />
end <br />
inner_box:allDone()<br />
box:node(inner_box)<br />
if data.sections.below == true then<br />
local sec_bottom = makeSection(frame, args, data, "below")<br />
box:node(sec_bottom)<br />
end <br />
box:allDone()<br />
return box <br />
end<br />
<br />
function orderStringtoNumber(array, val, num)<br />
if num > table.getn(array) then<br />
array[#array+1] = val<br />
else<br />
table.insert(array, num, val)<br />
end <br />
return array<br />
end <br />
<br />
function isJoinable(args, data)<br />
if args.more_participants == "NO" then<br />
data.fields.join = nil<br />
data.fields.endorse.style = "display:inline; float:right;"<br />
end <br />
return data<br />
end <br />
<br />
function deepCopyTable(data)<br />
-- the deep copy is a workaround step to avoid the restrictions placed on <br />
-- tables imported through loadData<br />
if type(data) ~= 'table' then return data end<br />
local res = {}<br />
for k,v in pairs(data) do<br />
if type(v) == 'table' then<br />
v = deepCopyTable(v)<br />
end<br />
res[k] = v<br />
end<br />
return res<br />
end<br />
<br />
function getPortalData(args)<br />
-- loads the relevant stylesheet, if a sub-template was called with a portal<br />
-- argument and a stylesheet exists with the same name. For example, calling <br />
-- {{#invoke:Probox/Idealab|main|portal=Idealab}} would load the<br />
-- Module:Probox/Idealab stylesheet<br />
local data_readOnly = {}<br />
local data_writable = {}<br />
if (args.portal and mw.title.makeTitle( 'Module', 'Probox/' .. args.portal).exists) then<br />
data_readOnly = mw.loadData("Module:Probox/" .. args.portal)<br />
else<br />
data_readOnly = mw.loadData("Module:Probox/Default")<br />
end <br />
-- data_writable = TableTools.shallowClone(data_readOnly)<br />
data_writable = deepCopyTable(data_readOnly)<br />
return data_writable<br />
end<br />
<br />
function string.starts(String,Start)<br />
return string.sub(String,1,string.len(Start))==Start<br />
end<br />
<br />
function stringToLowerCase(value)<br />
return mw.ustring.lower(value)<br />
end <br />
<br />
function stringSpacesToUnderscores(value)<br />
return mw.ustring.gsub(value, " ", "_")<br />
end<br />
<br />
function stringFirstCharToUpper(str)<br />
return (str:gsub("^%l", string.upper))<br />
end<br />
<br />
function TCTlookup(args)<br />
local tct_path = tostring(args.translations)<br />
--mw.log(mw.title.getCurrentTitle().subpageText)<br />
local tct_subpage = mw.title.getCurrentTitle().subpageText<br />
if tct_subpage == "en" then<br />
tct_path = tct_path .. "/" .. tct_subpage<br />
elseif (mw.title.new("Template:" .. args.translations .. "/" .. tct_subpage).exists and mw.language.isSupportedLanguage(tct_subpage)) then<br />
tct_path = tct_path .. "/" .. tct_subpage<br />
else<br />
tct_path = tct_path .. "/" .. "en"<br />
end<br />
-- mw.log(tct_path)<br />
return tct_path<br />
end <br />
<br />
function getRoleArgs(args, available_roles)<br />
-- returns:<br />
-- 1) a table of ordered values for valid role params, <br />
-- even if numbered nonsequentially<br />
-- 2) a table of all roles with at least 1 empty param, <br />
-- plus the default volunteer role<br />
local filled_role_data = {}<br />
local open_roles = {}<br />
if available_roles.default then -- some boxes have default role to join<br />
open_roles[available_roles.default] = true<br />
end <br />
for rd_key, rd_val in pairs(available_roles) do<br />
for a_key, a_val in pairs(args) do<br />
if string.starts(a_key, rd_key) then<br />
if string.len(a_val) == 0 then<br />
open_roles[rd_key] = true<br />
else <br />
if not filled_role_data[rd_key] then filled_role_data[rd_key] = {} end<br />
local arg_num = tonumber(a_key:match('^' .. rd_key .. '([1-9]%d*)$'))<br />
if arg_num then<br />
filled_role_data[rd_key] = orderStringtoNumber(filled_role_data[rd_key], a_val, arg_num)<br />
else<br />
table.insert(filled_role_data[rd_key], 1, a_val) <br />
end<br />
end <br />
end <br />
end<br />
end<br />
return filled_role_data, open_roles<br />
end <br />
<br />
function p.main(frame)<br />
local args = getArgs(frame, {removeBlanks = false})<br />
local data = getPortalData(args)<br />
data = isJoinable(args, data)<br />
if not (args.translations and mw.title.new("Template:" .. args.translations).exists) then<br />
args.translations = "Probox/Default/Content"<br />
end<br />
-- mw.log(args.translations)<br />
-- if the TCT content index is under translation, check for translations in the subpage language<br />
if mw.title.new("Template:" .. args.translations .. "/en").exists then <br />
args.translations = TCTlookup(args)<br />
end <br />
if data.sections.cta == true then<br />
args.talk = tostring(mw.title.getCurrentTitle().talkPageTitle) -- expensive<br />
end <br />
local filled_role_data, open_roles = getRoleArgs(args, data.roles)<br />
local box = makeInfobox(frame, args, data, filled_role_data, open_roles)<br />
local infobox = tostring(box)<br />
-- only add cats if not in Template or User ns<br />
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<br />
-- FIXME specify namespace in config, so that categories only appear if template is translcuded in that namespace<br />
page_categories = addCategories(data, args, open_roles)<br />
infobox = infobox .. page_categories<br />
end <br />
if data.templates then<br />
local top_template = addTemplates(frame, data, args)<br />
infobox = top_template .. infobox<br />
end <br />
return infobox<br />
end<br />
<br />
return p</div>Prahlad balaji