Module:T-CountryHub: Difference between revisions
Appearance
// via Wikitext Extension for VSCode Tag: Reverted |
// via Wikitext Extension for VSCode Tag: Reverted |
||
| Line 1: | Line 1: | ||
-- Modules/T-CountryHub.lua | -- Modules/T-CountryHub.lua | ||
-- Blueprint-based full-page template for country hubs with | -- Blueprint-based full-page template for country hubs with a Lua-driven masonry layout engine. | ||
-- ================================================================================ | |||
-- #region REQUIRES AND HELPERS | |||
-- ================================================================================ | |||
-- Safe require helpers | -- Safe require helpers | ||
local function safeRequire(name) | local function safeRequire(name) | ||
local ok, mod = pcall(require, name) | local ok, mod = pcall(require, name) | ||
if ok and mod then | if ok and mod then return mod end | ||
return setmetatable({}, { __index = function() return function() return '' end end }) | |||
return setmetatable({}, { | |||
end | end | ||
| Line 25: | Line 23: | ||
local mw = mw | local mw = mw | ||
local html = mw.html | local html = mw.html | ||
local smw = (mw.smw and mw.smw.ask) and mw.smw or nil | local smw = (mw.smw and mw.smw.ask) and mw.smw or nil | ||
-- askCached: Performs a Semantic MediaWiki #ask query with caching | -- askCached: Performs a Semantic MediaWiki #ask query with caching. | ||
local _askCache = {} | local _askCache = {} | ||
local function askCached(key, params) | local function askCached(key, params) | ||
| Line 40: | Line 35: | ||
end | end | ||
-- safeField: Safely retrieves a field from a data row | -- safeField: Safely retrieves a field from a data row. | ||
local function safeField(row, key) | local function safeField(row, key) | ||
if not row then return '' end | if not row then return '' end | ||
if row[key] ~= nil and row[key] ~= '' then return row[key] end | if row[key] ~= nil and row[key] ~= '' then return row[key] end | ||
if row[1] | if row[1] ~= nil and row[1] ~= '' then return row[1] end | ||
return '' | return '' | ||
end | end | ||
-- | -- #endregion | ||
-- | -- ================================================================================ | ||
-- | -- #region CORE TEMPLATE AND BLUEPRINT CONFIGURATION | ||
-- | -- ================================================================================ | ||
local errorContext = ErrorHandling.createContext("T-CountryHub") | local errorContext = ErrorHandling.createContext("T-CountryHub") | ||
local template = Blueprint.registerTemplate('CountryHub', { | local template = Blueprint.registerTemplate('CountryHub', { | ||
features = { | features = { | ||
fullPage = true, | fullPage = true, | ||
countryWrapper = true, | countryWrapper = true, | ||
infoBox = true, | infoBox = true, | ||
semanticProperties = true, | semanticProperties = true, | ||
categories = true, | categories = true, | ||
| Line 137: | Line 65: | ||
}) | }) | ||
Blueprint.initializeConfig(template) | Blueprint.initializeConfig(template) | ||
-- | -- The sequence now only controls the non-masonry parts of the page. | ||
-- The masonry content is handled by the new layout engine. | |||
template.config.blockSequence = { | template.config.blockSequence = { | ||
'wrapperOpen', | 'wrapperOpen', | ||
'infoBox', | 'infoBox', | ||
' | 'layoutController', -- This new block will render the entire masonry layout | ||
'wrapperClose', | 'wrapperClose', | ||
'categories', | 'categories', | ||
| Line 161: | Line 78: | ||
} | } | ||
template.config.blocks = template.config.blocks or {} | |||
-- #endregion | |||
-- ================================================================================ | |||
-- #region STATIC BLOCKS (WRAPPERS AND INFOBOX) | |||
-- ================================================================================ | -- ================================================================================ | ||
template.config.blocks.wrapperOpen = { | template.config.blocks.wrapperOpen = { | ||
feature = 'countryWrapper', | feature = 'countryWrapper', | ||
render = function( | render = function() return '<div class="country-hub-wrapper">' end | ||
} | |||
template.config.blocks.wrapperClose = { | |||
feature = 'countryWrapper', | |||
render = function() return '</div>' end | |||
} | } | ||
template.config.blocks.infoBox = { | template.config.blocks.infoBox = { | ||
feature = 'infoBox', | feature = 'infoBox', | ||
| Line 205: | Line 112: | ||
-- Fetch ICANN region for the country | -- Fetch ICANN region for the country | ||
local regionParams = { | local regionParams = { string.format('[[Has country::%s]]', args.has_country), '?Has ICANN region', format = 'plain', limit = 1 } | ||
local regionData = askCached('infoBox:region:' .. args.has_country, regionParams) | local regionData = askCached('infoBox:region:' .. args.has_country, regionParams) | ||
local regionText = regionData[1] and regionData[1]['Has ICANN region'] or '' | local regionText = regionData[1] and regionData[1]['Has ICANN region'] or '' | ||
| Line 216: | Line 118: | ||
-- Check for an ISOC chapter in the country using fuzzy matching | -- Check for an ISOC chapter in the country using fuzzy matching | ||
local ISOCParams = { | local ISOCParams = { '[[Category:Internet Society Chapter]]', limit = 200 } | ||
local ISOCData = askCached('infoBox:ISOC:all_chapters', ISOCParams) | local ISOCData = askCached('infoBox:ISOC:all_chapters', ISOCParams) | ||
local ISOCText | local ISOCText | ||
| Line 269: | Line 168: | ||
-- Check for a Youth IGF initiative in the country | -- Check for a Youth IGF initiative in the country | ||
local youthParams = { | local youthParams = { string.format('[[Category:Youth IGF %s]]', args.has_country), limit = 1 } | ||
local youthData = askCached('infoBox:youth:' .. args.has_country, youthParams) | local youthData = askCached('infoBox:youth:' .. args.has_country, youthParams) | ||
local youthText | local youthText = (youthData[1] and youthData[1]['result'] and youthData[1]['result'] ~= '') and youthData[1]['result'] or string.format('[[Youth IGF %s]]', args.has_country) | ||
local flagImageWikitext = "" | local flagImageWikitext = "" | ||
| Line 286: | Line 177: | ||
if isoCode and #isoCode == 2 then | if isoCode and #isoCode == 2 then | ||
local flagFile = "Flag-" .. string.lower(isoCode) .. ".svg" | local flagFile = "Flag-" .. string.lower(isoCode) .. ".svg" | ||
flagImageWikitext = string.format( | flagImageWikitext = string.format("[[File:%s|link=|class=country-infobox-bg-image]]", flagFile) | ||
end | end | ||
end | end | ||
-- Assemble the HTML for the infobox table | -- Assemble the HTML for the infobox table | ||
local infoBoxWrapper = html.create('div') | local infoBoxWrapper = html.create('div'):addClass('country-hub-infobox-wrapper') | ||
local infoBox = infoBoxWrapper:tag('table'):addClass('country-hub-infobox icannwiki-automatic-text') | |||
infoBox:tag('tr'):tag('th'):attr('colspan', '2'):addClass('country-hub-infobox-header icannwiki-automatic-text'):wikitext(string.format('%s', displayCountry)):done():done() | |||
local infoBox = infoBoxWrapper:tag('table') | infoBox:tag('tr'):tag('th'):wikitext('ccTLD'):done():tag('td'):wikitext(ccTLDText):done():done() | ||
infoBox:tag('tr'):tag('th'):wikitext('ICANN region'):done():tag('td'):wikitext(regionText):done():done() | |||
infoBox:tag('tr'):tag('th'):wikitext('ISOC chapter'):done():tag('td'):wikitext(ISOCText):done():done() | |||
infoBox:tag('tr'):tag('th'):wikitext('Youth IGF'):done():tag('td'):wikitext(youthText):done():done() | |||
infoBox:tag('tr') | |||
infoBox:tag('tr') | |||
infoBox:tag('tr') | |||
infoBox:tag('tr') | |||
infoBox:tag('tr') | |||
infoBoxWrapper:wikitext(flagImageWikitext) | infoBoxWrapper:wikitext(flagImageWikitext) | ||
| Line 341: | Line 195: | ||
} | } | ||
-- | -- #endregion | ||
-- | -- ================================================================================ | ||
-- #region LAYOUT ENGINE AND CONTENT BLOCK DEFINITIONS | |||
-- ================================================================================ | |||
-- | -- This table defines all the content blocks that will be part of the masonry layout. | ||
-- Each function fetches data and returns a block object for the layout engine. | |||
local contentBlocks = { | |||
intro = function(template, args) | |||
local | local rawIntroText = '<p>Welcome to ICANNWiki\'s hub for <b>{{PAGENAME}}</b>. With the use of semantics, this page aggregates all Internet Governance content for this territory that is currently indexed in our database.</p>' | ||
local processedText = template.current_frame:preprocess(rawIntroText) | |||
return { | |||
id = 'intro', | |||
height = 2, -- Assign a fixed height proxy for the intro text | |||
renderFunc = function() return processedText end | |||
} | } | ||
end, | |||
organizations = function(template, args) | |||
local params = { string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country), limit = 50 } | |||
local data = askCached('organizations:' .. args.has_country, params) | local data = askCached('organizations:' .. args.has_country, params) | ||
return | if #data == 0 then return nil end | ||
return { | |||
id = 'organizations', | |||
content = data, | |||
height = #data + 1, -- +1 for the header | |||
renderFunc = function(d) | |||
local t = html.create('table'):addClass('wikitable') | |||
t:tag('tr'):tag('th'):wikitext('Organizations'):done():done() | |||
for _, row in ipairs(d) do | |||
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done() | |||
end | |||
return tostring(t) | |||
end | |||
} | } | ||
end, | |||
people = function(template, args) | |||
local params = { string.format('[[Has country::%s]] [[Has entity type::Person]]', args.has_country), limit = 50 } | |||
local data = askCached('people:' .. args.has_country, params) | local data = askCached('people:' .. args.has_country, params) | ||
return | if #data == 0 then return nil end | ||
return { | |||
id = 'people', | |||
content = data, | |||
height = #data + 1, | |||
renderFunc = function(d) | |||
local t = html.create('table'):addClass('wikitable') | |||
t:tag('tr'):tag('th'):wikitext('People'):done():done() | |||
for _, row in ipairs(d) do | |||
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done() | |||
end | |||
return tostring(t) | |||
end | |||
} | |||
end, | end, | ||
geoTlds = function(template, args) | |||
local params = { string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country), limit = 50 } | |||
local data = askCached('geoTlds:' .. args.has_country, params) | |||
if #data == 0 then return nil end | |||
return { | |||
id = 'geoTlds', | |||
local params = { | content = data, | ||
height = #data + 1, | |||
renderFunc = function(d) | |||
local t = html.create('table'):addClass('wikitable') | |||
t:tag('tr'):tag('th'):wikitext('GeoTLDs'):done():done() | |||
for _, row in ipairs(d) do | |||
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done() | |||
end | |||
return tostring(t) | |||
end | |||
} | } | ||
end, | end, | ||
meetings = function(template, args) | |||
local params = { string.format('[[Has country::%s]] [[Has entity type::Event]]', args.has_country), limit = 50 } | |||
local data = askCached('events:' .. args.has_country, params) | |||
if #data == 0 then return nil end | |||
return { | |||
id = 'meetings', | |||
local params = { | content = data, | ||
height = #data + 1, | |||
renderFunc = function(d) | |||
local t = html.create('table'):addClass('wikitable') | |||
t:tag('tr'):tag('th'):wikitext('Internet Governance Events'):done():done() | |||
for _, row in ipairs(d) do | |||
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done() | |||
end | |||
return tostring(t) | |||
end | |||
} | } | ||
end, | end, | ||
nra = function(template, args) | |||
local params = { string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country), limit = 10 } | |||
local data = askCached('nra:' .. args.has_country, params) | |||
if #data == 0 then return nil end | |||
return { | |||
id = 'nra', | |||
content = data, | |||
height = #data + 1, | |||
renderFunc = function(d) | |||
local t = html.create('table'):addClass('wikitable') | |||
t:tag('tr'):tag('th'):wikitext('National Authorities'):done():done() | |||
for _, row in ipairs(d) do | |||
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done() | |||
end | |||
return tostring(t) | |||
end | |||
} | |||
end | |||
} | } | ||
-- | -- The main controller block that drives the masonry layout. | ||
template.config.blocks. | template.config.blocks.layoutController = { | ||
feature = ' | feature = 'fullPage', | ||
render = function(template, args) | render = function(template, args) | ||
local | local numColumns = 3 -- Define the number of columns | ||
-- 1. Collect all available content blocks | |||
local availableBlocks = {} | |||
return | local blockSequenceForLayout = {'intro', 'organizations', 'people', 'geoTlds', 'meetings', 'nra'} | ||
for _, blockId in ipairs(blockSequenceForLayout) do | |||
local blockFunc = contentBlocks[blockId] | |||
if blockFunc then | |||
local blockData = blockFunc(template, args) | |||
if blockData then | |||
table.insert(availableBlocks, blockData) | |||
end | |||
end | |||
end | |||
if #availableBlocks == 0 then return '' end | |||
-- | -- 2. Sort blocks by height, descending. This is key for the greedy algorithm. | ||
table.sort(availableBlocks, function(a, b) | |||
return a.height > b.height | |||
end) | |||
-- | -- 3. Distribute blocks into columns using a greedy algorithm | ||
local columns = {} | |||
for i = 1, numColumns do | |||
columns[i] = { height = 0, blocks = {} } | |||
end | |||
-- -- | for _, block in ipairs(availableBlocks) do | ||
-- Find the shortest column | |||
local shortestColumnIndex = 1 | |||
for i = 2, numColumns do | |||
if columns[i].height < columns[shortestColumnIndex].height then | |||
shortestColumnIndex = i | |||
end | |||
end | |||
-- Add the block to the shortest column | |||
table.insert(columns[shortestColumnIndex].blocks, block) | |||
columns[shortestColumnIndex].height = columns[shortestColumnIndex].height + block.height | |||
end | |||
-- | -- 4. Render the final HTML | ||
local root = html.create('div'):addClass('country-hub-data-container') | |||
for _, col in ipairs(columns) do | |||
if #col.blocks > 0 then | |||
local columnDiv = root:tag('div'):addClass('country-hub-column') | |||
for _, block in ipairs(col.blocks) do | |||
columnDiv:wikitext(block.renderFunc(block.content)) | |||
end | |||
end | |||
end | |||
return | |||
end | return tostring(root) | ||
end | |||
} | } | ||
-- | -- #endregion | ||
-- | -- ================================================================================ | ||
-- #region PREPROCESSORS AND PROVIDERS | |||
-- ================================================================================ | |||
Blueprint.addPreprocessor(template, function(_, args) | Blueprint.addPreprocessor(template, function(_, args) | ||
args.country = args.country or mw.title.getCurrentTitle().text or "" | args.country = args.country or mw.title.getCurrentTitle().text or "" | ||
| Line 507: | Line 378: | ||
end) | end) | ||
Blueprint.registerPropertyProvider(template, function(_, args) | Blueprint.registerPropertyProvider(template, function(_, args) | ||
local props = {} | local props = {} | ||
| Line 517: | Line 387: | ||
end) | end) | ||
Blueprint.registerCategoryProvider(template, function(_, args) | Blueprint.registerCategoryProvider(template, function(_, args) | ||
local cats = {"Country Hub"} | local cats = {"Country Hub"} | ||
| Line 527: | Line 396: | ||
end) | end) | ||
-- | -- #endregion | ||
-- ================================================================================ | |||
-- #region RENDER ENTRY POINT | |||
-- ================================================================================ | |||
function p.render(frame) | function p.render(frame) | ||
return ErrorHandling.protect( | return ErrorHandling.protect( | ||
| Line 538: | Line 412: | ||
return p | return p | ||
-- #endregion | |||
Revision as of 15:07, 6 June 2025
Documentation for this module may be created at Module:T-CountryHub/doc
-- Modules/T-CountryHub.lua
-- Blueprint-based full-page template for country hubs with a Lua-driven masonry layout engine.
-- ================================================================================
-- #region REQUIRES AND HELPERS
-- ================================================================================
-- Safe require helpers
local function safeRequire(name)
local ok, mod = pcall(require, name)
if ok and mod then return mod end
return setmetatable({}, { __index = function() return function() return '' end end })
end
-- Requires
local p = {}
local Blueprint = safeRequire('Module:LuaTemplateBlueprint')
local ErrorHandling = safeRequire('Module:ErrorHandling')
local CountryData = safeRequire('Module:CountryData')
local TemplateHelpers = safeRequire('Module:TemplateHelpers')
local NormalizationText = safeRequire('Module:NormalizationText')
local NormalizationDiacritic = safeRequire('Module:NormalizationDiacritic')
local mw = mw
local html = mw.html
local smw = (mw.smw and mw.smw.ask) and mw.smw or nil
-- askCached: Performs a Semantic MediaWiki #ask query with caching.
local _askCache = {}
local function askCached(key, params)
if not smw then return {} end
local cacheKey = TemplateHelpers.generateCacheKey('CountryHub:ask', key)
return TemplateHelpers.withCache(cacheKey, function()
return smw.ask(params) or {}
end)
end
-- safeField: Safely retrieves a field from a data row.
local function safeField(row, key)
if not row then return '' end
if row[key] ~= nil and row[key] ~= '' then return row[key] end
if row[1] ~= nil and row[1] ~= '' then return row[1] end
return ''
end
-- #endregion
-- ================================================================================
-- #region CORE TEMPLATE AND BLUEPRINT CONFIGURATION
-- ================================================================================
local errorContext = ErrorHandling.createContext("T-CountryHub")
local template = Blueprint.registerTemplate('CountryHub', {
features = {
fullPage = true,
countryWrapper = true,
infoBox = true,
semanticProperties = true,
categories = true,
errorReporting = true
},
constants = {
tableClass = ""
}
})
Blueprint.initializeConfig(template)
-- The sequence now only controls the non-masonry parts of the page.
-- The masonry content is handled by the new layout engine.
template.config.blockSequence = {
'wrapperOpen',
'infoBox',
'layoutController', -- This new block will render the entire masonry layout
'wrapperClose',
'categories',
'errors'
}
template.config.blocks = template.config.blocks or {}
-- #endregion
-- ================================================================================
-- #region STATIC BLOCKS (WRAPPERS AND INFOBOX)
-- ================================================================================
template.config.blocks.wrapperOpen = {
feature = 'countryWrapper',
render = function() return '<div class="country-hub-wrapper">' end
}
template.config.blocks.wrapperClose = {
feature = 'countryWrapper',
render = function() return '</div>' end
}
template.config.blocks.infoBox = {
feature = 'infoBox',
render = function(template, args)
local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
-- Derive ccTLD from ISO code via CountryData module
local isoCode = CountryData.getCountryCodeByName(args.has_country)
local ccTLDText
if isoCode and #isoCode == 2 then
local cctldValue = "." .. string.lower(isoCode)
ccTLDText = "[[" .. cctldValue .. "]]" -- Make it a wiki link
else
ccTLDText = string.format('ccTLD data unavailable for %s.', displayCountry)
end
-- Fetch ICANN region for the country
local regionParams = { string.format('[[Has country::%s]]', args.has_country), '?Has ICANN region', format = 'plain', limit = 1 }
local regionData = askCached('infoBox:region:' .. args.has_country, regionParams)
local regionText = regionData[1] and regionData[1]['Has ICANN region'] or ''
regionText = NormalizationText.processWikiLink(regionText, 'strip')
-- Check for an ISOC chapter in the country using fuzzy matching
local ISOCParams = { '[[Category:Internet Society Chapter]]', limit = 200 }
local ISOCData = askCached('infoBox:ISOC:all_chapters', ISOCParams)
local ISOCText
local function normalizeForMatching(str)
if not str then return '' end
local pageName = string.match(str, '^%[%[([^%]]+)%]%]$') or str
pageName = NormalizationDiacritic.removeDiacritics(pageName)
pageName = string.lower(pageName)
pageName = string.gsub(pageName, 'internet society', '')
pageName = string.gsub(pageName, 'chapter', '')
return NormalizationText.trim(pageName)
end
local countryData = CountryData.getCountryByName(args.has_country)
local countryNamesToMatch = {}
if countryData then
table.insert(countryNamesToMatch, normalizeForMatching(countryData.name))
if countryData.variations then
for _, variation in pairs(countryData.variations) do
table.insert(countryNamesToMatch, normalizeForMatching(variation))
end
end
else
table.insert(countryNamesToMatch, normalizeForMatching(args.has_country))
end
local foundMatch = false
if ISOCData and #ISOCData > 0 then
for _, chapter in ipairs(ISOCData) do
local chapterName = chapter.result
if chapterName and chapterName ~= '' then
local normalizedChapterName = normalizeForMatching(chapterName)
for _, countryNameToMatch in ipairs(countryNamesToMatch) do
if string.find(normalizedChapterName, countryNameToMatch, 1, true) then
ISOCText = chapterName
foundMatch = true
break
end
end
end
if foundMatch then break end
end
end
if not foundMatch then
ISOCText = string.format('[[Internet Society %s Chapter]]', args.has_country)
end
-- Check for a Youth IGF initiative in the country
local youthParams = { string.format('[[Category:Youth IGF %s]]', args.has_country), limit = 1 }
local youthData = askCached('infoBox:youth:' .. args.has_country, youthParams)
local youthText = (youthData[1] and youthData[1]['result'] and youthData[1]['result'] ~= '') and youthData[1]['result'] or string.format('[[Youth IGF %s]]', args.has_country)
local flagImageWikitext = ""
if args.has_country and args.has_country ~= "" then
local isoCode = CountryData.getCountryCodeByName(args.has_country)
if isoCode and #isoCode == 2 then
local flagFile = "Flag-" .. string.lower(isoCode) .. ".svg"
flagImageWikitext = string.format("[[File:%s|link=|class=country-infobox-bg-image]]", flagFile)
end
end
-- Assemble the HTML for the infobox table
local infoBoxWrapper = html.create('div'):addClass('country-hub-infobox-wrapper')
local infoBox = infoBoxWrapper:tag('table'):addClass('country-hub-infobox icannwiki-automatic-text')
infoBox:tag('tr'):tag('th'):attr('colspan', '2'):addClass('country-hub-infobox-header icannwiki-automatic-text'):wikitext(string.format('%s', displayCountry)):done():done()
infoBox:tag('tr'):tag('th'):wikitext('ccTLD'):done():tag('td'):wikitext(ccTLDText):done():done()
infoBox:tag('tr'):tag('th'):wikitext('ICANN region'):done():tag('td'):wikitext(regionText):done():done()
infoBox:tag('tr'):tag('th'):wikitext('ISOC chapter'):done():tag('td'):wikitext(ISOCText):done():done()
infoBox:tag('tr'):tag('th'):wikitext('Youth IGF'):done():tag('td'):wikitext(youthText):done():done()
infoBoxWrapper:wikitext(flagImageWikitext)
return tostring(infoBoxWrapper)
end
}
-- #endregion
-- ================================================================================
-- #region LAYOUT ENGINE AND CONTENT BLOCK DEFINITIONS
-- ================================================================================
-- This table defines all the content blocks that will be part of the masonry layout.
-- Each function fetches data and returns a block object for the layout engine.
local contentBlocks = {
intro = function(template, args)
local rawIntroText = '<p>Welcome to ICANNWiki\'s hub for <b>{{PAGENAME}}</b>. With the use of semantics, this page aggregates all Internet Governance content for this territory that is currently indexed in our database.</p>'
local processedText = template.current_frame:preprocess(rawIntroText)
return {
id = 'intro',
height = 2, -- Assign a fixed height proxy for the intro text
renderFunc = function() return processedText end
}
end,
organizations = function(template, args)
local params = { string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country), limit = 50 }
local data = askCached('organizations:' .. args.has_country, params)
if #data == 0 then return nil end
return {
id = 'organizations',
content = data,
height = #data + 1, -- +1 for the header
renderFunc = function(d)
local t = html.create('table'):addClass('wikitable')
t:tag('tr'):tag('th'):wikitext('Organizations'):done():done()
for _, row in ipairs(d) do
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
end
return tostring(t)
end
}
end,
people = function(template, args)
local params = { string.format('[[Has country::%s]] [[Has entity type::Person]]', args.has_country), limit = 50 }
local data = askCached('people:' .. args.has_country, params)
if #data == 0 then return nil end
return {
id = 'people',
content = data,
height = #data + 1,
renderFunc = function(d)
local t = html.create('table'):addClass('wikitable')
t:tag('tr'):tag('th'):wikitext('People'):done():done()
for _, row in ipairs(d) do
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
end
return tostring(t)
end
}
end,
geoTlds = function(template, args)
local params = { string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country), limit = 50 }
local data = askCached('geoTlds:' .. args.has_country, params)
if #data == 0 then return nil end
return {
id = 'geoTlds',
content = data,
height = #data + 1,
renderFunc = function(d)
local t = html.create('table'):addClass('wikitable')
t:tag('tr'):tag('th'):wikitext('GeoTLDs'):done():done()
for _, row in ipairs(d) do
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
end
return tostring(t)
end
}
end,
meetings = function(template, args)
local params = { string.format('[[Has country::%s]] [[Has entity type::Event]]', args.has_country), limit = 50 }
local data = askCached('events:' .. args.has_country, params)
if #data == 0 then return nil end
return {
id = 'meetings',
content = data,
height = #data + 1,
renderFunc = function(d)
local t = html.create('table'):addClass('wikitable')
t:tag('tr'):tag('th'):wikitext('Internet Governance Events'):done():done()
for _, row in ipairs(d) do
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
end
return tostring(t)
end
}
end,
nra = function(template, args)
local params = { string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country), limit = 10 }
local data = askCached('nra:' .. args.has_country, params)
if #data == 0 then return nil end
return {
id = 'nra',
content = data,
height = #data + 1,
renderFunc = function(d)
local t = html.create('table'):addClass('wikitable')
t:tag('tr'):tag('th'):wikitext('National Authorities'):done():done()
for _, row in ipairs(d) do
t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
end
return tostring(t)
end
}
end
}
-- The main controller block that drives the masonry layout.
template.config.blocks.layoutController = {
feature = 'fullPage',
render = function(template, args)
local numColumns = 3 -- Define the number of columns
-- 1. Collect all available content blocks
local availableBlocks = {}
local blockSequenceForLayout = {'intro', 'organizations', 'people', 'geoTlds', 'meetings', 'nra'}
for _, blockId in ipairs(blockSequenceForLayout) do
local blockFunc = contentBlocks[blockId]
if blockFunc then
local blockData = blockFunc(template, args)
if blockData then
table.insert(availableBlocks, blockData)
end
end
end
if #availableBlocks == 0 then return '' end
-- 2. Sort blocks by height, descending. This is key for the greedy algorithm.
table.sort(availableBlocks, function(a, b)
return a.height > b.height
end)
-- 3. Distribute blocks into columns using a greedy algorithm
local columns = {}
for i = 1, numColumns do
columns[i] = { height = 0, blocks = {} }
end
for _, block in ipairs(availableBlocks) do
-- Find the shortest column
local shortestColumnIndex = 1
for i = 2, numColumns do
if columns[i].height < columns[shortestColumnIndex].height then
shortestColumnIndex = i
end
end
-- Add the block to the shortest column
table.insert(columns[shortestColumnIndex].blocks, block)
columns[shortestColumnIndex].height = columns[shortestColumnIndex].height + block.height
end
-- 4. Render the final HTML
local root = html.create('div'):addClass('country-hub-data-container')
for _, col in ipairs(columns) do
if #col.blocks > 0 then
local columnDiv = root:tag('div'):addClass('country-hub-column')
for _, block in ipairs(col.blocks) do
columnDiv:wikitext(block.renderFunc(block.content))
end
end
end
return tostring(root)
end
}
-- #endregion
-- ================================================================================
-- #region PREPROCESSORS AND PROVIDERS
-- ================================================================================
Blueprint.addPreprocessor(template, function(_, args)
args.country = args.country or mw.title.getCurrentTitle().text or ""
args.has_country = CountryData.normalizeCountryName(args.country)
args.region = CountryData.getRegionByCountry(args.country)
return args
end)
Blueprint.registerPropertyProvider(template, function(_, args)
local props = {}
if args.has_country and args.has_country ~= "(Unrecognized)" then
props["Has country"] = args.has_country
props["Has ICANN region"] = args.region
end
return props
end)
Blueprint.registerCategoryProvider(template, function(_, args)
local cats = {"Country Hub"}
if args.has_country and args.has_country ~= "(Unrecognized)" then
table.insert(cats, args.has_country)
table.insert(cats, args.region)
end
return cats
end)
-- #endregion
-- ================================================================================
-- #region RENDER ENTRY POINT
-- ================================================================================
function p.render(frame)
return ErrorHandling.protect(
errorContext, "render",
function() return template.render(frame) end,
ErrorHandling.getMessage("TEMPLATE_RENDER_ERROR"),
frame
)
end
return p
-- #endregion