https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AProbox%2Fsandbox Module:Probox/sandbox - Revision history 2025-06-02T10:19:00Z Revision history for this page on the wiki MediaWiki 1.45.0-wmf.3 https://en.wikipedia.org/w/index.php?title=Module:Probox/sandbox&diff=750605187&oldid=prev Prahlad balaji: Create sandbox version of Module:Probox 2016-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(&#039;Module:Arguments&#039;).getArgs<br /> local TableTools = require(&#039;Module:TableTools&#039;)<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(&#039;div&#039;) <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]) &gt; 0 and type(data.templates[k]) == &quot;table&quot;) 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(&quot;&quot;)<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(&quot;Template:&quot; .. tmplt_val).exists) then <br /> template_div<br /> :cssText(&quot;margin-bottom:1em;&quot;)<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) == &quot;string&quot; and stringToLowerCase(args[k]) == tmplt_key and mw.title.new(&quot;Template:&quot; .. 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(&quot;^%l&quot;, 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]) &gt; 0) then --convert args to lowercase before checking them against cats<br /> if type(data.categories[k]) == &quot;table&quot; 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]) == &quot;string&quot; 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 = &quot; [[&quot; .. tostring(table.concat(cat_list, &quot;]] [[&quot;)) .. &quot;]]&quot;<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 == &quot;body&quot; then<br /> field_div<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style2)--inconsistent use of styles 2/3 here<br /> :wikitext(field.title)--we probably aren&#039;t using this for most things now, after Heather&#039;s redesign <br /> :done()<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style3)<br /> :wikitext(field.values[1])<br /> :done() <br /> elseif field.vtype2 == &quot;title&quot; then<br /> field_div<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style3)<br /> :wikitext(field.values[1])<br /> :done() <br /> elseif field.vtype2 == &quot;link&quot; then<br /> field_div<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style3) <br /> :wikitext(&quot;[[&quot; .. field.values[1] .. &quot;|&lt;span style=&#039;&quot; .. field.style2 .. &quot;&#039;&gt;&quot; .. field.title ..&quot;&lt;/span&gt;]]&quot;)<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(&#039;span&#039;)<br /> :cssText(field.style3)<br /> if field.vtype2 == &quot;thumb&quot; then <br /> field_div <br /> :wikitext(&quot;[[&quot; .. field.values[1] .. &quot;|right|&quot; .. field.width ..&quot;]]&quot;)<br /> elseif field.vtype2 == &quot;link&quot; then<br /> field_div<br /> :wikitext(&quot;[[&quot; .. field.values[1] .. &quot;|&quot; .. field.alignment .. &quot;|&quot; .. field.width ..&quot;|link=&quot; .. field.link .. &quot;]]&quot;)<br /> elseif field.vtype2 == &quot;badge&quot; then<br /> if mw.ustring.find( field.values[1], &quot;http&quot;, 1, true ) then<br /> field_div<br /> :wikitext(&quot;[[&quot; .. field.icon .. &quot;|&quot; .. field.alignment .. &quot;|&quot; .. field.width ..&quot;|link=&quot; .. field.values[1] .. &quot;]] &quot; .. &quot;[&quot; .. field.values[1] .. &quot; &quot; .. field.title .. &quot;]&quot;)<br /> end <br /> elseif field.vtype2 == &quot;ui_button&quot; 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(&#039;div&#039;)<br /> local title_span<br /> if field.icon then <br /> title_span = &quot;[[&quot; .. field.icon .. &quot;|left&quot; .. &quot;|18px]] &quot; .. field.title<br /> else<br /> title_span = field.title<br /> end <br /> field_div<br /> :cssText(field.style)<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style2)<br /> :wikitext(title_span)<br /> :done()<br /> if ftype == &quot;filled&quot; then<br /> local i = 1<br /> for k,v in ipairs(field.values) do<br /> if (i &gt; 1 and field.icon) then --only insert extra padding if has icon<br /> field.style3 = &quot;padding-left:25px; display:block&quot;<br /> end<br /> if field.vtype2 then --ideally all configs should at least have this field for participants. FIXME<br /> if field.vtype2 == &quot;username&quot; then<br /> v = &quot;• &quot; .. &quot;[[User:&quot; .. v .. &quot;|&quot; .. v .. &quot;]]&quot;<br /> elseif field.vtype2 == &quot;email&quot; then<br /> v = &quot;• &quot; .. v<br /> end<br /> else<br /> v = &quot;• &quot; .. &quot;[[User:&quot; .. v .. &quot;|&quot; .. v .. &quot;]]&quot;<br /> end <br /> field_div<br /> :tag(&#039;span&#039;)<br /> :cssText(field.style3)<br /> :wikitext(v) <br /> -- :wikitext(&quot;• &quot; .. &quot;[[User:&quot; .. v .. &quot;|&quot; .. v .. &quot;]]&quot;)<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(&#039;div&#039;):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, &quot;filled&quot;) <br /> filled_role_fields[field.rank] = filled_field_div <br /> end<br /> local sec_div = makeSectionDiv(filled_role_fields, data.styles.section[&quot;participants&quot;]) <br /> if (data.fields.more_participants and args.more_participants and stringToLowerCase(args.more_participants)) == &quot;yes&quot; then -- really need this here?<br /> -- if (args.portal == &quot;Idealab&quot; or args.portal == &quot;Research&quot;) then -- beware, exceptions everywhere<br /> sec_div:tag(&#039;span&#039;):cssText(&quot;font-style:italic; color: #888888&quot;):wikitext(mw.text.trim(frame:expandTemplate{title=args.translations, args={data.fields.more_participants.key}})):done()<br /> -- elseif args.portal == &quot;Patterns&quot; then <br /> -- sec_div:tag(&#039;span&#039;):cssText(&quot;font-style:italic; color: #888888&quot;):wikitext(&quot;a learning pattern for...&quot;):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, &quot;open&quot;) <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(&#039;div&#039;):cssText(field.style) --why declare this here?<br /> if field.vtype == &quot;image&quot; then<br /> if (field.isRequired == true or (args[field.arg] and string.len(args[field.arg]) &gt; 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 == &quot;text&quot; then<br /> field_div = makeTextField(field, field_div) <br /> else<br /> end <br /> return field_div -- make sure div is &#039;done&#039;<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(&#039;div&#039;):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]) &gt; 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 == &quot;text&quot; 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&#039;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(&#039;div&#039;):cssText(data.styles.box.outer)<br /> local inner_box = mw.html.create(&#039;div&#039;):cssText(data.styles.box.inner)<br /> if data.sections.above == true then<br /> local sec_top = makeSection(frame, args, data, &quot;above&quot;)<br /> box:node(sec_top)<br /> end <br /> if data.sections.nav == true then<br /> local sec_nav = makeSection(frame, args, data, &quot;nav&quot;)<br /> box:node(sec_nav)<br /> end <br /> local sec_head = makeSection(frame, args, data, &quot;head&quot;)<br /> inner_box:node(sec_head)<br /> local sec_main = makeSection(frame, args, data, &quot;main&quot;)<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, &quot;cta&quot;)<br /> inner_box:node(sec_cta)<br /> inner_box:tag(&#039;div&#039;):cssText(&quot;clear:both&quot;):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, &quot;below&quot;)<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 &gt; 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 == &quot;NO&quot; then<br /> data.fields.join = nil<br /> data.fields.endorse.style = &quot;display:inline; float:right;&quot;<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) ~= &#039;table&#039; then return data end<br /> local res = {}<br /> for k,v in pairs(data) do<br /> if type(v) == &#039;table&#039; 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( &#039;Module&#039;, &#039;Probox/&#039; .. args.portal).exists) then<br /> data_readOnly = mw.loadData(&quot;Module:Probox/&quot; .. args.portal)<br /> else<br /> data_readOnly = mw.loadData(&quot;Module:Probox/Default&quot;)<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, &quot; &quot;, &quot;_&quot;)<br /> end<br /> <br /> function stringFirstCharToUpper(str)<br /> return (str:gsub(&quot;^%l&quot;, 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 == &quot;en&quot; then<br /> tct_path = tct_path .. &quot;/&quot; .. tct_subpage<br /> elseif (mw.title.new(&quot;Template:&quot; .. args.translations .. &quot;/&quot; .. tct_subpage).exists and mw.language.isSupportedLanguage(tct_subpage)) then<br /> tct_path = tct_path .. &quot;/&quot; .. tct_subpage<br /> else<br /> tct_path = tct_path .. &quot;/&quot; .. &quot;en&quot;<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(&#039;^&#039; .. rd_key .. &#039;([1-9]%d*)$&#039;))<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(&quot;Template:&quot; .. args.translations).exists) then<br /> args.translations = &quot;Probox/Default/Content&quot;<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(&quot;Template:&quot; .. args.translations .. &quot;/en&quot;).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 ~= &quot;Template&quot; and mw.title.getCurrentTitle().nsText ~= &quot;User&quot; and mw.title.getCurrentTitle().nsText ~= &quot;Meta&quot;) 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