Module:T-CountryHub: Difference between revisions

// 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 discrete content blocks
-- 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 mod
     return setmetatable({}, { __index = function() return function() return '' end end })
    end
     return setmetatable({}, {
        __index = function()
            return function() return '' end
        end
    })
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 -- Handle for Semantic MediaWiki's #ask functionality, if available
local smw          = (mw.smw and mw.smw.ask) and mw.smw or nil


-- 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.
-- askCached: Performs a Semantic MediaWiki #ask query with caching.
-- @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 _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; 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).
-- safeField: Safely retrieves a field from a data row.
-- @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)
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]     ~= nil and row[1]     ~= '' then return row[1] end -- Fallback for SMW results where data might be in the first unnamed field
     if row[1]   ~= nil and row[1]   ~= '' then return row[1] end
     return ''
     return ''
end
end


-- renderTable: Generates HTML for a standard MediaWiki table
-- #endregion
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.
-- #region CORE TEMPLATE AND BLUEPRINT CONFIGURATION
-- @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
 
-- -- generateProcessedBrowseLink: Constructs and preprocesses a MediaWiki link for browsing data, with {{fullurl:...}} correctly expanded by MediaWiki before the link is rendered.
-- local function generateProcessedBrowseLink(template, browseType, browseQueryParam, linkTextPattern, country)
--    -- Explicitly URL-encode the country name for use in the URL query parameter
--    local encodedCountry = mw.uri.encode(country, 'QUERY') -- 'QUERY' mode is for query string values
 
--    local browseLinkString = string.format(
--        '[{{fullurl:Special:BrowseData/%s|%s=%s}} %s →]',
--        browseType,
--        browseQueryParam,
--        encodedCountry,  -- Use the encoded country name for the URL part
--        string.format(linkTextPattern, country) -- Use the original country name for the display text
--    )
   
--    if template.current_frame and template.current_frame.preprocess then
--        local ok, result = pcall(function() return template.current_frame:preprocess(browseLinkString) end)
--        if ok then
--            return result
--        else
--            return browseLinkString -- Fallback on preprocess error
--        end
--    end
--    return browseLinkString -- Fallback if no frame or preprocess
-- end


local errorContext = ErrorHandling.createContext("T-CountryHub")
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', {
local template = Blueprint.registerTemplate('CountryHub', {
     features = {
     features = {
         fullPage = true, -- does not render as a template box, but rather occupies the entire page
         fullPage = true,
         countryWrapper = true,
         countryWrapper = true,
        countryFlag = true,
         infoBox = true,
         infoBox = true,
        intro = true,
        overview = false,
        organizations = true,
        people = true,
        laws = false,
        documents = false,
        geoTlds = true,
        meetings = true,
        nra = true,
        resources = false,
         semanticProperties = true,
         semanticProperties = true,
         categories = true,
         categories = true,
Line 137: Line 65:
})
})


-- Blueprint default: Initialize standard configuration
Blueprint.initializeConfig(template)
Blueprint.initializeConfig(template)


-- CONTROL THE VISUAL ORDER THAT EACH ASPECT IS RENDERED IN
-- 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',
     'overview',
     'layoutController', -- This new block will render the entire masonry layout
    'dataWrapperOpen',
    'intro',
    'organizations',
    'people',
    'geoTlds',
    'meetings',
    'nra',
    'laws',
    'documents',
    'resources',
    'dataWrapperClose',
     '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 = 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 = {
template.config.blocks.wrapperOpen = {
     feature = 'countryWrapper',
     feature = 'countryWrapper',
     render  = function(template, args) -- Added template, args
     render  = function() return '<div class="country-hub-wrapper">' end
        -- local normalizedCountryName = args.has_country -- From preprocessor
}
        -- local flagImageWikitext = ""


        -- if normalizedCountryName and normalizedCountryName ~= "" then
template.config.blocks.wrapperClose = {
        --    local isoCode = CountryData.getCountryCodeByName(normalizedCountryName)
     feature = 'countryWrapper',
        --     if isoCode and #isoCode == 2 then
    render  = function() return '</div>' end
        --        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
}
}


-- ANCHOR: INFOBOX
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 }
            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 }
            '[[Category:Internet Society Chapter]]',
            limit = 200 -- Fetch all chapters to perform fuzzy matching
        }
         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 }
            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)
        if youthData[1] and youthData[1]['result'] and youthData[1]['result'] ~= '' then
            youthText = youthData[1]['result']
        else
            youthText = string.format('[[Youth IGF %s]]', args.has_country)
        end
          
          
         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)
                    "[[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')
            :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()
            :addClass('country-hub-infobox icannwiki-automatic-text')
         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()
        -- Header row
         infoBox:tag('tr'):tag('th'):wikitext('Youth IGF'):done():tag('td'):wikitext(youthText):done():done()
         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()
       
        -- Youth IGF row
         infoBox:tag('tr')
            :tag('th'):wikitext('Youth IGF'):done()
            :tag('td'):wikitext(youthText):done()
            :done()
       
         infoBoxWrapper:wikitext(flagImageWikitext)
         infoBoxWrapper:wikitext(flagImageWikitext)
          
          
Line 341: Line 195:
}
}


-- ANCHOR: INTRO
-- #endregion
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 = {
-- #region LAYOUT ENGINE AND CONTENT BLOCK DEFINITIONS
    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
-- This table defines all the content blocks that will be part of the masonry layout.
template.config.blocks.organizations = {
-- Each function fetches data and returns a block object for the layout engine.
     feature = 'organizations',
local contentBlocks = {
    render = function(template, args)
     intro = function(template, args)
         local params = {
         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>'
            string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country),
        local processedText = template.current_frame:preprocess(rawIntroText)
             limit    = 50
        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 renderTable(data, {'Organizations'})
        if #data == 0 then return nil end
    end
         return {
}
            id = 'organizations',
 
            content = data,
-- ANCHOR: PEOPLE
            height = #data + 1, -- +1 for the header
template.config.blocks.people = {
            renderFunc = function(d)
    feature = 'people',
                local t = html.create('table'):addClass('wikitable')
    render = function(template, args)
                t:tag('tr'):tag('th'):wikitext('Organizations'):done():done()
        local params = {
                for _, row in ipairs(d) do
            string.format('[[Has country::%s]] [[Has entity type::Person]]',  
                    t:tag('tr'):tag('td'):wikitext(safeField(row, 'result')):done():done()
            args.has_country),
                end
             limit    = 20
                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 renderTable(data, {'People'})
        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 }
-- ANCHOR: REGULATIONS
        local data = askCached('geoTlds:' .. args.has_country, params)
template.config.blocks.laws = {
        if #data == 0 then return nil end
    feature = 'laws',
        return {
     render = function(template, args)
             id = 'geoTlds',
         local params = {
            content = data,
            string.format('[[Has country::%s]]', args.has_country),
            height = #data + 1,
            '[[Category:Laws]]',
            renderFunc = function(d)
             mainlabel = 'Law', sort = 'Has date', order = 'desc',
                local t = html.create('table'):addClass('wikitable')
             limit    = 50
                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
         }
         }
        return renderSection('laws:' .. args.has_country, params, {'Laws and Regulations'})
     end,
     end,
}
     meetings = function(template, args)
 
         local params = { string.format('[[Has country::%s]] [[Has entity type::Event]]', args.has_country), limit = 50 }
-- ANCHOR: DOCUMENTS
        local data = askCached('events:' .. args.has_country, params)
template.config.blocks.documents = {
        if #data == 0 then return nil end
    feature = 'documents',
        return {
     render = function(template, args)
             id = 'meetings',
         local params = {
            content = data,
            string.format('[[Has country::%s]]', args.has_country),
            height = #data + 1,
            '[[Category:Document]]',
            renderFunc = function(d)
             mainlabel = 'Document', sort = 'Has date', order = 'desc',
                local t = html.create('table'):addClass('wikitable')
             limit    = 50
                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
         }
         }
        return renderSection('documents:' .. args.has_country, params, {'Key Documents'})
     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
}
}


-- ANCHOR: GEOTLDS
-- The main controller block that drives the masonry layout.
template.config.blocks.geoTlds = {
template.config.blocks.layoutController = {
     feature = 'geoTlds',
     feature = 'fullPage',
     render = function(template, args)
     render = function(template, args)
         local params = {
         local numColumns = 3 -- Define the number of columns
             string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country),
       
             limit    = 50
        -- 1. Collect all available content blocks
         }
        local availableBlocks = {}
         return renderSection('geoTlds:' .. args.has_country, params, {'GeoTLDs'})
        local blockSequenceForLayout = {'intro', 'organizations', 'people', 'geoTlds', 'meetings', 'nra'}
    end,
        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


-- ANCHOR: EVENTS
        -- 2. Sort blocks by height, descending. This is key for the greedy algorithm.
template.config.blocks.meetings = {
        table.sort(availableBlocks, function(a, b)
    feature = 'meetings',
             return a.height > b.height
    render = function(template, args)
         end)
        local params = {
             string.format('[[Has country::%s]]', args.has_country),
            '[[Has entity type::Event]]',
            limit    = 50
         }
        return renderSection('events:' .. args.has_country, params, {'Internet Governance Events'})
    end,
}


-- ANCHOR: AUTHORITIES
        -- 3. Distribute blocks into columns using a greedy algorithm
template.config.blocks.nra = {
        local columns = {}
    feature = 'nra',
        for i = 1, numColumns do
    render = function(template, args)
             columns[i] = { height = 0, blocks = {} }
        local params = {
         end
             string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country),
            limit    = 10
        }
         return renderSection('nra:' .. args.has_country, params, {'National Authorities'})
    end,
}


-- -- REVIEW: CONNECTED COUNTRIES
        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


-- ANCHOR: RESOURCES
        -- 4. Render the final HTML
template.config.blocks.resources = {
        local root = html.create('div'):addClass('country-hub-data-container')
    feature = 'resources',
         for _, col in ipairs(columns) do
    render = function(template, args)
             if #col.blocks > 0 then
         local params = {
                local columnDiv = root:tag('div'):addClass('country-hub-column')
             string.format('[[Has country::%s]]', args.has_country),
                for _, block in ipairs(col.blocks) do
            '[[Category:Resource]]',
                    columnDiv:wikitext(block.renderFunc(block.content))
             mainlabel = 'Resource',
                end
            limit    = 10
             end
         }
        end
         return renderSection('resources:' .. args.has_country, params, {'Resources'})
          
     end,
         return tostring(root)
     end
}
}


-- Data wrapper blocks
-- #endregion
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 = {
-- #region PREPROCESSORS AND PROVIDERS
    feature = 'countryWrapper',
-- ================================================================================
    render  = function() return '</div>' 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)
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)


-- Semantic property provider: Sets 'Has country' and 'Has ICANN region' semantic properties
Blueprint.registerPropertyProvider(template, function(_, args)
Blueprint.registerPropertyProvider(template, function(_, args)
     local props = {}
     local props = {}
Line 517: Line 387:
end)
end)


-- Category provider
Blueprint.registerCategoryProvider(template, function(_, args)
Blueprint.registerCategoryProvider(template, function(_, args)
     local cats = {"Country Hub"}
     local cats = {"Country Hub"}
Line 527: Line 396:
end)
end)


-- Render entry point; wraps the Blueprint rendering process with error protection
-- #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