Module:T-CountryHub: Difference between revisions
Appearance
// via Wikitext Extension for VSCode |
Maintenance update // via Wikitext Extension for VSCode |
||
| (42 intermediate revisions by the same user not shown) | |||
| Line 21: | Line 21: | ||
local CountryData = safeRequire('Module:CountryData') | local CountryData = safeRequire('Module:CountryData') | ||
local TemplateHelpers = safeRequire('Module:TemplateHelpers') | local TemplateHelpers = safeRequire('Module:TemplateHelpers') | ||
local LinkParser = safeRequire('Module:LinkParser') | |||
local NormalizationText = safeRequire('Module:NormalizationText') | local NormalizationText = safeRequire('Module:NormalizationText') | ||
local NormalizationDiacritic = safeRequire('Module:NormalizationDiacritic') | local NormalizationDiacritic = safeRequire('Module:NormalizationDiacritic') | ||
local MasonryLayout = safeRequire('Module:MasonryLayout') | |||
local mw = mw | local mw = mw | ||
local html = mw.html | local html = mw.html | ||
| Line 69: | Line 71: | ||
end | end | ||
-- | -- renderSection: A wrapper function that performs a cached SMW query and renders a table only if results are found. | ||
-- | -- @param key string: The base cache key for the query. | ||
-- @param params table: The parameters for the smw.ask query. | |||
-- | -- @param columns table: A list of column headers for the table. | ||
-- @return string: An HTML table if data is found, otherwise an empty string. | |||
local function renderSection(key, params, columns) | |||
-- | local data = askCached(key, params) | ||
if not data or #data == 0 then | |||
return '' | |||
end | |||
-- | return renderTable(data, columns) | ||
end | |||
local errorContext = ErrorHandling.createContext("T-CountryHub") | local errorContext = ErrorHandling.createContext("T-CountryHub") | ||
| Line 110: | Line 101: | ||
people = true, | people = true, | ||
laws = true, | laws = true, | ||
documents = | documents = false, | ||
geoTlds = true, | geoTlds = true, | ||
meetings = true, | meetings = true, | ||
nra = true, | nra = true, | ||
resources = | resources = false, | ||
semanticProperties = true, | semanticProperties = true, | ||
categories = true, | categories = true, | ||
| Line 130: | Line 121: | ||
template.config.blockSequence = { | template.config.blockSequence = { | ||
'wrapperOpen', | 'wrapperOpen', | ||
' | 'featureBanner', | ||
'overview', | 'overview', | ||
' | 'intelligentMasonry', | ||
'wrapperClose', | 'wrapperClose', | ||
'categories', | 'categories', | ||
'errors' | 'errors' | ||
} | |||
-- MASONRY LAYOUT CONFIGURATION | |||
local cardDefinitions = { | |||
{blockId = 'intro', feature = 'intro', title = 'Welcome'}, | |||
{blockId = 'organizations', feature = 'organizations', title = 'Organizations'}, | |||
{blockId = 'people', feature = 'people', title = 'People'}, | |||
{blockId = 'geoTlds', feature = 'geoTlds', title = 'GeoTLDs'}, | |||
{blockId = 'meetings', feature = 'meetings', title = 'Internet Governance Events'}, | |||
{blockId = 'nra', feature = 'nra', title = 'National Authorities'}, | |||
{blockId = 'laws', feature = 'laws', title = 'Laws and Regulations'}, | |||
{blockId = 'documents', feature = 'documents', title = 'Key Documents'}, | |||
{blockId = 'resources', feature = 'resources', title = 'Resources'}, | |||
{blockId = 'infoBox', feature = 'infoBox', title = 'Country Info'} | |||
} | |||
local masonryOptions = { | |||
columns = 3, | |||
mobileColumns = 1, | |||
containerClass = 'country-hub-masonry-container', | |||
columnClass = 'country-hub-masonry-column', | |||
cardClass = 'country-hub-masonry-card', | |||
-- Note: We cannot detect mobile server-side in MediaWiki | |||
-- The MasonryLayout will output both desktop and mobile HTML | |||
-- CSS media queries will handle the actual display | |||
mobileMode = false -- Always use desktop mode in Lua, CSS handles responsive | |||
} | } | ||
| Line 170: | Line 179: | ||
return '<div class="country-hub-wrapper">' -- .. flagImageWikitext -- Appended flag | return '<div class="country-hub-wrapper">' -- .. flagImageWikitext -- Appended flag | ||
end | |||
} | |||
-- Feature Preview Banner | |||
template.config.blocks.featureBanner = { | |||
feature = 'fullPage', | |||
render = function(template, args) | |||
return '<div class="country-hub-feature-banner">' .. | |||
'<strong>Country Hubs</strong> have been enabled as a feature preview and are still under testing.' .. | |||
' Contribute more knowledge to our database so that they can keep growing!' .. | |||
'</div>' | |||
end | end | ||
} | } | ||
| Line 178: | Line 198: | ||
render = function(template, args) | render = function(template, args) | ||
local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ') | local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ') | ||
-- Derive ccTLD from ISO code via CountryData module | -- Derive ccTLD from ISO code via CountryData module | ||
local isoCode = CountryData.getCountryCodeByName( | local isoCode = CountryData.getCountryCodeByName(args.has_country) | ||
local ccTLDText | local ccTLDText | ||
if isoCode and #isoCode == 2 then | if isoCode and #isoCode == 2 then | ||
| Line 192: | Line 211: | ||
-- Fetch ICANN region for the country | -- Fetch ICANN region for the country | ||
local regionParams = { | local regionParams = { | ||
string.format('[[Has country::%s]]', | string.format('[[Has country::%s]]', args.has_country), | ||
'?Has ICANN region', | '?Has ICANN region', | ||
format = 'plain', | format = 'plain', | ||
limit = 1 | limit = 1 | ||
} | } | ||
local regionData = askCached('infoBox:region:' .. | 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 '' | ||
regionText = | regionText = LinkParser.processWikiLink(regionText, 'strip') | ||
-- Check for an ISOC chapter in the country using fuzzy matching | -- Check for an ISOC chapter in the country using fuzzy matching | ||
| Line 219: | Line 238: | ||
end | end | ||
local countryData = CountryData.getCountryByName( | local countryData = CountryData.getCountryByName(args.has_country) | ||
local countryNamesToMatch = {} | local countryNamesToMatch = {} | ||
if countryData then | if countryData then | ||
| Line 229: | Line 248: | ||
end | end | ||
else | else | ||
table.insert(countryNamesToMatch, normalizeForMatching( | table.insert(countryNamesToMatch, normalizeForMatching(args.has_country)) | ||
end | end | ||
| Line 251: | Line 270: | ||
if not foundMatch then | if not foundMatch then | ||
ISOCText = string.format('[[Internet Society %s Chapter]]', | ISOCText = string.format('[[Internet Society %s Chapter]]', args.has_country) | ||
end | end | ||
local flagImageWikitext = "" | local flagImageWikitext = "" | ||
if | if args.has_country and args.has_country ~= "" then | ||
local isoCode = CountryData.getCountryCodeByName( | local isoCode = CountryData.getCountryCodeByName(args.has_country) | ||
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" | ||
| Line 313: | Line 319: | ||
:tag('th'):wikitext('ISOC chapter'):done() | :tag('th'):wikitext('ISOC chapter'):done() | ||
:tag('td'):wikitext(ISOCText):done() | :tag('td'):wikitext(ISOCText):done() | ||
:done() | :done() | ||
| Line 340: | Line 340: | ||
-- ANCHOR: OVERVIEW | -- ANCHOR: OVERVIEW | ||
template.config.blocks.overview = { | |||
feature = 'overview', | |||
render = function(template, args) | |||
local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ') | |||
local queryCountryName = args.has_country | |||
local params = { | |||
string.format('[[Has country::%s]]', queryCountryName), | |||
'[[Category:Country]]', | |||
'?Has description', | |||
format = 'plain', | |||
limit = 1 | |||
} | |||
local data = askCached('overview:' .. queryCountryName, params) | |||
local desc = (data[1] and data[1]['Has description']) or '' | |||
if desc == '' then | |||
desc = 'No overview description found for ' .. displayCountry .. '.' | |||
end | |||
return '== Overview ==\n' .. desc | |||
end | |||
} | |||
-- ANCHOR: ORGANIZATIONS | -- ANCHOR: ORGANIZATIONS | ||
| Line 365: | Line 365: | ||
feature = 'organizations', | feature = 'organizations', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]] [[Has entity type::Organization]]', | string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country), | ||
limit = | limit = 20 | ||
} | } | ||
local data = askCached('organizations:' .. | local data = askCached('organizations:' .. args.has_country, params) | ||
-- Store the raw count for masonry layout | |||
-- | template._rawDataCounts = template._rawDataCounts or {} | ||
template._rawDataCounts.organizations = #data | |||
-- Only render if we have data | |||
if #data == 0 then | |||
return '' | |||
-- | end | ||
return renderTable(data, {'Organizations'}) | |||
end | end | ||
} | } | ||
| Line 391: | Line 385: | ||
feature = 'people', | feature = 'people', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]] [[Has entity type::Person]]', | string.format('[[Has country::%s]] [[Has entity type::Person]]', | ||
args.has_country), | |||
limit = 20 | limit = 20 | ||
} | } | ||
local data = askCached('people:' .. | local data = askCached('people:' .. args.has_country, params) | ||
-- Store the raw count for masonry layout | |||
-- | template._rawDataCounts = template._rawDataCounts or {} | ||
template._rawDataCounts.people = #data | |||
-- Only render if we have data | |||
if #data == 0 then | |||
return '' | |||
-- | end | ||
return renderTable(data, {'People'}) | |||
end, | end, | ||
} | } | ||
| Line 420: | Line 406: | ||
feature = 'laws', | feature = 'laws', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]] | string.format('[[Has country::%s]] [[Has entity type::Norm]]', args.has_country), | ||
limit = 20 | |||
limit = | |||
} | } | ||
local data = askCached('laws:' .. | local data = askCached('laws:' .. args.has_country, params) | ||
-- Store the raw count for masonry layout | |||
-- | template._rawDataCounts = template._rawDataCounts or {} | ||
template._rawDataCounts.laws = #data | |||
-- Only render if we have data | |||
-- | if #data == 0 then | ||
return '' | |||
end | |||
return renderTable(data, {'Laws and Regulations'}) | |||
return ' | |||
end, | end, | ||
} | } | ||
| Line 445: | Line 426: | ||
feature = 'documents', | feature = 'documents', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]]', | string.format('[[Has country::%s]]', args.has_country), | ||
'[[Category:Document]]', | '[[Category:Document]]', | ||
mainlabel = 'Document', sort = 'Has date', order = 'desc', | mainlabel = 'Document', sort = 'Has date', order = 'desc', | ||
limit = | limit = 20 | ||
} | } | ||
return renderSection('documents:' .. args.has_country, params, {'Key Documents'}) | |||
end, | end, | ||
} | } | ||
| Line 470: | Line 440: | ||
feature = 'geoTlds', | feature = 'geoTlds', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', | string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country), | ||
limit = | limit = 20 | ||
} | } | ||
local data = askCached('geoTlds:' .. | local data = askCached('geoTlds:' .. args.has_country, params) | ||
-- Store the raw count for masonry layout | |||
-- | template._rawDataCounts = template._rawDataCounts or {} | ||
template._rawDataCounts.geoTlds = #data | |||
-- Only render if we have data | |||
-- | if #data == 0 then | ||
return '' | |||
end | |||
return renderTable(data, {'GeoTLDs'}) | |||
return ' | |||
end, | end, | ||
} | } | ||
| Line 493: | Line 460: | ||
feature = 'meetings', | feature = 'meetings', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]]', | string.format('[[Has country::%s]]', args.has_country), | ||
'[[Has entity type::Event]]', | '[[Has entity type::Event]]', | ||
limit = 20 | |||
limit = | |||
} | } | ||
local data = askCached('events:' .. args.has_country, params) | |||
-- Store the raw count for masonry layout | |||
template._rawDataCounts = template._rawDataCounts or {} | |||
template._rawDataCounts.meetings = #data | |||
-- Only render if we have data | |||
if #data == 0 then | |||
return '' | |||
local data = askCached('events:' .. | |||
return ' | |||
end | end | ||
return renderTable(data, {'Internet Governance Events'}) | |||
end, | end, | ||
} | } | ||
| Line 526: | Line 481: | ||
feature = 'nra', | feature = 'nra', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', | string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country), | ||
limit = | limit = 20 | ||
} | } | ||
local data = askCached('nra:' .. | local data = askCached('nra:' .. args.has_country, params) | ||
-- Store the raw count for masonry layout | |||
template._rawDataCounts = template._rawDataCounts or {} | |||
template._rawDataCounts.nra = #data | |||
-- Only render if we have data | |||
if #data == 0 then | |||
return '' | |||
end | |||
return renderTable(data, {'National Authorities'}) | |||
end, | end, | ||
} | } | ||
-- -- REVIEW: CONNECTED COUNTRIES | -- -- REVIEW: CONNECTED COUNTRIES (COLONIES) | ||
-- ANCHOR: RESOURCES | -- ANCHOR: RESOURCES | ||
| Line 544: | Line 503: | ||
feature = 'resources', | feature = 'resources', | ||
render = function(template, args) | render = function(template, args) | ||
local params = { | local params = { | ||
string.format('[[Has country::%s]]', | string.format('[[Has country::%s]]', args.has_country), | ||
'[[Category:Resource]]', | '[[Category:Resource]]', | ||
mainlabel = 'Resource', | mainlabel = 'Resource', | ||
limit = | limit = 20 | ||
} | } | ||
return renderSection('resources:' .. args.has_country, params, {'Resources'}) | |||
end, | end, | ||
} | |||
-- Data wrapper blocks | |||
template.config.blocks.dataWrapperOpen = { | |||
feature = 'fullPage', | |||
render = function() return '<div class="country-hub-data-container">' end | |||
} | |||
template.config.blocks.dataWrapperClose = { | |||
feature = 'fullPage', | |||
render = function() return '</div>' end | |||
} | } | ||
| Line 562: | Line 527: | ||
feature = 'countryWrapper', | feature = 'countryWrapper', | ||
render = function() return '</div>' end | render = function() return '</div>' end | ||
} | |||
-- INTELLIGENT MASONRY LAYOUT INTEGRATION | |||
-- Single block that handles all masonry logic at render-time (Blueprint pattern) | |||
template.config.blocks.intelligentMasonry = { | |||
feature = 'fullPage', | |||
render = function(template, args) | |||
return MasonryLayout.renderIntelligentLayout(template, args, { | |||
cardDefinitions = cardDefinitions, | |||
options = masonryOptions, | |||
blockRenderers = { | |||
intro = template.config.blocks.intro, | |||
organizations = template.config.blocks.organizations, | |||
people = template.config.blocks.people, | |||
geoTlds = template.config.blocks.geoTlds, | |||
meetings = template.config.blocks.meetings, | |||
nra = template.config.blocks.nra, | |||
laws = template.config.blocks.laws, | |||
documents = template.config.blocks.documents, | |||
resources = template.config.blocks.resources, | |||
infoBox = template.config.blocks.infoBox | |||
} | |||
}) | |||
end | |||
} | } | ||
Latest revision as of 13:55, 6 January 2026
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 discrete content blocks
-- 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 LinkParser = safeRequire('Module:LinkParser')
local NormalizationText = safeRequire('Module:NormalizationText')
local NormalizationDiacritic = safeRequire('Module:NormalizationDiacritic')
local MasonryLayout = safeRequire('Module:MasonryLayout')
local mw = mw
local html = mw.html
local smw = (mw.smw and mw.smw.ask) and mw.smw or nil -- Handle for Semantic MediaWiki's #ask functionality, if available
-- askCached: Performs a Semantic MediaWiki #ask query with caching; uses TemplateHelpers.generateCacheKey to create a unique key for the query and TemplateHelpers.withCache to manage the caching logic.
-- @param key string: A unique identifier part for the query, used for generating the cache key.
-- @param params table: Parameters to be passed to the smw.ask function.
-- @return table: The result of the SMW query, or an empty table if SMW is unavailable or the query fails.
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; it first tries to access the field by its string 'key'. If not found or empty, it falls back to the first element of the row (index 1).
-- @param row table: The data row (a table).
-- @param key string/number: The key or index of the field to retrieve.
-- @return string: The field's value, or an empty string if not found or if the row is nil.
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 -- Fallback for SMW results where data might be in the first unnamed field
return ''
end
-- renderTable: Generates HTML for a standard MediaWiki table
local function renderTable(result, columns)
local t = html.create('table'):addClass('wikitable')
-- Header row
local header = t:tag('tr')
for _, col in ipairs(columns) do
header:tag('th'):wikitext(col):done()
end
-- Data rows
for _, row in ipairs(result) do
local tr = t:tag('tr')
for _, col in ipairs(columns) do
tr:tag('td'):wikitext(safeField(row, col)):done()
end
end
return tostring(t)
end
-- renderSection: A wrapper function that performs a cached SMW query and renders a table only if results are found.
-- @param key string: The base cache key for the query.
-- @param params table: The parameters for the smw.ask query.
-- @param columns table: A list of column headers for the table.
-- @return string: An HTML table if data is found, otherwise an empty string.
local function renderSection(key, params, columns)
local data = askCached(key, params)
if not data or #data == 0 then
return ''
end
return renderTable(data, columns)
end
local errorContext = ErrorHandling.createContext("T-CountryHub")
-- ================================================================================
-- CONTROL OF TEMPLATE FEATURES: THIS LIST SPECIFIES IN AN EXPLICIT MANNER WHAT FEATURES ARE TO BE CALLED/RENDERED BY THE TEMPLATE.
local template = Blueprint.registerTemplate('CountryHub', {
features = {
fullPage = true, -- does not render as a template box, but rather occupies the entire page
countryWrapper = true,
countryFlag = true,
infoBox = true,
intro = true,
overview = false,
organizations = true,
people = true,
laws = true,
documents = false,
geoTlds = true,
meetings = true,
nra = true,
resources = false,
semanticProperties = true,
categories = true,
errorReporting = true
},
constants = {
tableClass = ""
}
})
-- Blueprint default: Initialize standard configuration
Blueprint.initializeConfig(template)
-- CONTROL THE VISUAL ORDER THAT EACH ASPECT IS RENDERED IN
template.config.blockSequence = {
'wrapperOpen',
'featureBanner',
'overview',
'intelligentMasonry',
'wrapperClose',
'categories',
'errors'
}
-- MASONRY LAYOUT CONFIGURATION
local cardDefinitions = {
{blockId = 'intro', feature = 'intro', title = 'Welcome'},
{blockId = 'organizations', feature = 'organizations', title = 'Organizations'},
{blockId = 'people', feature = 'people', title = 'People'},
{blockId = 'geoTlds', feature = 'geoTlds', title = 'GeoTLDs'},
{blockId = 'meetings', feature = 'meetings', title = 'Internet Governance Events'},
{blockId = 'nra', feature = 'nra', title = 'National Authorities'},
{blockId = 'laws', feature = 'laws', title = 'Laws and Regulations'},
{blockId = 'documents', feature = 'documents', title = 'Key Documents'},
{blockId = 'resources', feature = 'resources', title = 'Resources'},
{blockId = 'infoBox', feature = 'infoBox', title = 'Country Info'}
}
local masonryOptions = {
columns = 3,
mobileColumns = 1,
containerClass = 'country-hub-masonry-container',
columnClass = 'country-hub-masonry-column',
cardClass = 'country-hub-masonry-card',
-- Note: We cannot detect mobile server-side in MediaWiki
-- The MasonryLayout will output both desktop and mobile HTML
-- CSS media queries will handle the actual display
mobileMode = false -- Always use desktop mode in Lua, CSS handles responsive
}
-- ================================================================================
template.config.blocks = template.config.blocks or {}
-- Wrapper open block: Defines the opening div for the main content wrapper and includes the flag image
template.config.blocks.wrapperOpen = {
feature = 'countryWrapper',
render = function(template, args) -- Added template, args
-- local normalizedCountryName = args.has_country -- From preprocessor
-- local flagImageWikitext = ""
-- if normalizedCountryName and normalizedCountryName ~= "" then
-- local isoCode = CountryData.getCountryCodeByName(normalizedCountryName)
-- if isoCode and #isoCode == 2 then
-- local flagFile = "Flag-" .. string.lower(isoCode) .. ".svg"
-- -- Using a new class 'country-wrapper-bg-image' for the image
-- flagImageWikitext = string.format(
-- "[[File:%s|link=|class=country-wrapper-bg-image]]",
-- flagFile
-- )
-- end
-- end
return '<div class="country-hub-wrapper">' -- .. flagImageWikitext -- Appended flag
end
}
-- Feature Preview Banner
template.config.blocks.featureBanner = {
feature = 'fullPage',
render = function(template, args)
return '<div class="country-hub-feature-banner">' ..
'<strong>Country Hubs</strong> have been enabled as a feature preview and are still under testing.' ..
' Contribute more knowledge to our database so that they can keep growing!' ..
'</div>'
end
}
-- ANCHOR: INFOBOX
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 = LinkParser.processWikiLink(regionText, 'strip')
-- Check for an ISOC chapter in the country using fuzzy matching
local ISOCParams = {
'[[Category:Internet Society Chapter]]',
limit = 200 -- Fetch all chapters to perform fuzzy matching
}
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
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')
-- Header row
infoBox:tag('tr')
:tag('th')
:attr('colspan', '2')
:addClass('country-hub-infobox-header icannwiki-automatic-text')
:wikitext(string.format('%s', displayCountry))
:done()
:done()
-- ccTLD row
infoBox:tag('tr')
:tag('th'):wikitext('ccTLD'):done()
:tag('td'):wikitext(ccTLDText):done()
:done()
-- ICANN region row
infoBox:tag('tr')
:tag('th'):wikitext('ICANN region'):done()
:tag('td'):wikitext(regionText):done()
:done()
-- REVIEW: Check for ccNSO membership or affiliation in the country / https://ccnso.icann.org/en/about/members.htm
-- ISOC chapter row
infoBox:tag('tr')
:tag('th'):wikitext('ISOC chapter'):done()
:tag('td'):wikitext(ISOCText):done()
:done()
infoBoxWrapper:wikitext(flagImageWikitext)
return tostring(infoBoxWrapper)
end
}
-- ANCHOR: INTRO
template.config.blocks.intro = {
feature = 'intro',
render = 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>'
if template.current_frame and template.current_frame.preprocess then
return template.current_frame:preprocess(rawIntroText)
end
return rawIntroText
end
}
-- ANCHOR: OVERVIEW
template.config.blocks.overview = {
feature = 'overview',
render = function(template, args)
local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
local queryCountryName = args.has_country
local params = {
string.format('[[Has country::%s]]', queryCountryName),
'[[Category:Country]]',
'?Has description',
format = 'plain',
limit = 1
}
local data = askCached('overview:' .. queryCountryName, params)
local desc = (data[1] and data[1]['Has description']) or ''
if desc == '' then
desc = 'No overview description found for ' .. displayCountry .. '.'
end
return '== Overview ==\n' .. desc
end
}
-- ANCHOR: ORGANIZATIONS
template.config.blocks.organizations = {
feature = 'organizations',
render = function(template, args)
local params = {
string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country),
limit = 20
}
local data = askCached('organizations:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.organizations = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'Organizations'})
end
}
-- ANCHOR: PEOPLE
template.config.blocks.people = {
feature = 'people',
render = function(template, args)
local params = {
string.format('[[Has country::%s]] [[Has entity type::Person]]',
args.has_country),
limit = 20
}
local data = askCached('people:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.people = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'People'})
end,
}
-- ANCHOR: REGULATIONS
template.config.blocks.laws = {
feature = 'laws',
render = function(template, args)
local params = {
string.format('[[Has country::%s]] [[Has entity type::Norm]]', args.has_country),
limit = 20
}
local data = askCached('laws:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.laws = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'Laws and Regulations'})
end,
}
-- ANCHOR: DOCUMENTS
template.config.blocks.documents = {
feature = 'documents',
render = function(template, args)
local params = {
string.format('[[Has country::%s]]', args.has_country),
'[[Category:Document]]',
mainlabel = 'Document', sort = 'Has date', order = 'desc',
limit = 20
}
return renderSection('documents:' .. args.has_country, params, {'Key Documents'})
end,
}
-- ANCHOR: GEOTLDS
template.config.blocks.geoTlds = {
feature = 'geoTlds',
render = function(template, args)
local params = {
string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country),
limit = 20
}
local data = askCached('geoTlds:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.geoTlds = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'GeoTLDs'})
end,
}
-- ANCHOR: EVENTS
template.config.blocks.meetings = {
feature = 'meetings',
render = function(template, args)
local params = {
string.format('[[Has country::%s]]', args.has_country),
'[[Has entity type::Event]]',
limit = 20
}
local data = askCached('events:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.meetings = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'Internet Governance Events'})
end,
}
-- ANCHOR: AUTHORITIES
template.config.blocks.nra = {
feature = 'nra',
render = function(template, args)
local params = {
string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country),
limit = 20
}
local data = askCached('nra:' .. args.has_country, params)
-- Store the raw count for masonry layout
template._rawDataCounts = template._rawDataCounts or {}
template._rawDataCounts.nra = #data
-- Only render if we have data
if #data == 0 then
return ''
end
return renderTable(data, {'National Authorities'})
end,
}
-- -- REVIEW: CONNECTED COUNTRIES (COLONIES)
-- ANCHOR: RESOURCES
template.config.blocks.resources = {
feature = 'resources',
render = function(template, args)
local params = {
string.format('[[Has country::%s]]', args.has_country),
'[[Category:Resource]]',
mainlabel = 'Resource',
limit = 20
}
return renderSection('resources:' .. args.has_country, params, {'Resources'})
end,
}
-- Data wrapper blocks
template.config.blocks.dataWrapperOpen = {
feature = 'fullPage',
render = function() return '<div class="country-hub-data-container">' end
}
template.config.blocks.dataWrapperClose = {
feature = 'fullPage',
render = function() return '</div>' end
}
-- Wrapper close block
template.config.blocks.wrapperClose = {
feature = 'countryWrapper',
render = function() return '</div>' end
}
-- INTELLIGENT MASONRY LAYOUT INTEGRATION
-- Single block that handles all masonry logic at render-time (Blueprint pattern)
template.config.blocks.intelligentMasonry = {
feature = 'fullPage',
render = function(template, args)
return MasonryLayout.renderIntelligentLayout(template, args, {
cardDefinitions = cardDefinitions,
options = masonryOptions,
blockRenderers = {
intro = template.config.blocks.intro,
organizations = template.config.blocks.organizations,
people = template.config.blocks.people,
geoTlds = template.config.blocks.geoTlds,
meetings = template.config.blocks.meetings,
nra = template.config.blocks.nra,
laws = template.config.blocks.laws,
documents = template.config.blocks.documents,
resources = template.config.blocks.resources,
infoBox = template.config.blocks.infoBox
}
})
end
}
-- Preprocessor: Seeds 'country', 'has_country' (normalized), and 'region' arguments; ensures these key values are available and standardized early in the rendering process
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)
-- Semantic property provider: Sets 'Has country' and 'Has ICANN region' semantic properties
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)
-- Category provider
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)
-- Render entry point; wraps the Blueprint rendering process with error protection
function p.render(frame)
return ErrorHandling.protect(
errorContext, "render",
function() return template.render(frame) end,
ErrorHandling.getMessage("TEMPLATE_RENDER_ERROR"),
frame
)
end
return p