Module:T-CountryHub: Difference between revisions

// via Wikitext Extension for VSCode
Maintenance update // via Wikitext Extension for VSCode
 
(48 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


-- generateProcessedBrowseLink: Constructs and preprocesses a MediaWiki link for browsing data, with {{fullurl:...}} correctly expanded by MediaWiki before the link is rendered.
-- renderSection: A wrapper function that performs a cached SMW query and renders a table only if results are found.
local function generateProcessedBrowseLink(template, browseType, browseQueryParam, linkTextPattern, country)
-- @param key string: The base cache key for the query.
    -- Explicitly URL-encode the country name for use in the URL query parameter
-- @param params table: The parameters for the smw.ask query.
    local encodedCountry = mw.uri.encode(country, 'QUERY') -- 'QUERY' mode is for query string values
-- @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 browseLinkString = string.format(
local function renderSection(key, params, columns)
        '[{{fullurl:Special:BrowseData/%s|%s=%s}} %s →]',
     local data = askCached(key, params)
        browseType,
    if not data or #data == 0 then
        browseQueryParam,
         return ''
        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
     end
     return browseLinkString -- Fallback if no frame or preprocess
     return renderTable(data, columns)
end
end


Line 110: Line 101:
         people = true,
         people = true,
         laws = true,
         laws = true,
         documents = true,
         documents = false,
         geoTlds = true,
         geoTlds = true,
         meetings = true,
         meetings = true,
         nra = true,
         nra = true,
         resources = true,
         resources = false,
         semanticProperties = true,
         semanticProperties = true,
         categories = true,
         categories = true,
Line 130: Line 121:
template.config.blockSequence = {
template.config.blockSequence = {
     'wrapperOpen',
     'wrapperOpen',
     'infoBox',
     'featureBanner',
    'intro',
     'overview',
     'overview',
     'organizations',
     'intelligentMasonry',
    'people',
    'laws',
    'documents',
    'geoTlds',
    'meetings',
    'nra',
    'resources',
     '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 154: Line 163:
     feature = 'countryWrapper',
     feature = 'countryWrapper',
     render  = function(template, args) -- Added template, args
     render  = function(template, args) -- Added template, args
         local normalizedCountryName = args.has_country -- From preprocessor
         -- local normalizedCountryName = args.has_country -- From preprocessor
         local flagImageWikitext = ""
         -- local flagImageWikitext = ""


         if normalizedCountryName and normalizedCountryName ~= "" then
         -- if normalizedCountryName and normalizedCountryName ~= "" then
            local isoCode = CountryData.getCountryCodeByName(normalizedCountryName)
        --    local isoCode = CountryData.getCountryCodeByName(normalizedCountryName)
            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"
                -- Using a new class 'country-wrapper-bg-image' for the image
        --        -- Using a new class 'country-wrapper-bg-image' for the image
                flagImageWikitext = string.format(
        --        flagImageWikitext = string.format(
                    "[[File:%s|link=|class=country-wrapper-bg-image]]",  
        --            "[[File:%s|link=|class=country-wrapper-bg-image]]",  
                    flagFile
        --            flagFile
                )
        --        )
            end
        --    end
         end
         -- end
          
          
         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('_',' ')
        local queryCountryName = args.has_country
          
          
         -- Derive ccTLD from ISO code via CountryData module
         -- Derive ccTLD from ISO code via CountryData module
         local isoCode = CountryData.getCountryCodeByName(queryCountryName)
         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]]', queryCountryName),
             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:' .. queryCountryName, 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 ''
        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 218: Line 238:
         end
         end


         local countryData = CountryData.getCountryByName(queryCountryName)
         local countryData = CountryData.getCountryByName(args.has_country)
         local countryNamesToMatch = {}
         local countryNamesToMatch = {}
         if countryData then
         if countryData then
Line 228: Line 248:
             end
             end
         else
         else
             table.insert(countryNamesToMatch, normalizeForMatching(queryCountryName))
             table.insert(countryNamesToMatch, normalizeForMatching(args.has_country))
         end
         end


Line 250: Line 270:


         if not foundMatch then
         if not foundMatch then
             ISOCText = string.format('[[Internet Society %s Chapter]]', queryCountryName)
             ISOCText = string.format('[[Internet Society %s Chapter]]', args.has_country)
         end
         end
          
          
        -- Check for a Youth IGF initiative in the country
         local flagImageWikitext = ""
         local youthParams = {
        if args.has_country and args.has_country ~= "" then
            string.format('[[Category:Youth IGF %s]]', queryCountryName),
             local isoCode = CountryData.getCountryCodeByName(args.has_country)
             limit = 1
            if isoCode and #isoCode == 2 then
        }
                local flagFile = "Flag-" .. string.lower(isoCode) .. ".svg"
        local youthData = askCached('infoBox:youth:' .. queryCountryName, youthParams)
                flagImageWikitext = string.format(
        local youthText
                    "[[File:%s|link=|class=country-infobox-bg-image]]",  
        if youthData[1] and youthData[1]['result'] and youthData[1]['result'] ~= '' then
                    flagFile
            youthText = youthData[1]['result']
                )
        else
            end
            youthText = string.format('[[Youth IGF %s]]', queryCountryName)
         end
         end
        -- Assemble the HTML for the infobox table
        local infoBoxWrapper = html.create('div')
            :addClass('country-hub-infobox-wrapper')
          
          
        -- Assemble the HTML for the infobox table
         local infoBox = infoBoxWrapper:tag('table')
         local infoBox = html.create('table')
             :addClass('country-hub-infobox icannwiki-automatic-text')
             :addClass('country-hub-infobox icannwiki-automatic-text')
          
          
Line 281: Line 303:
         -- ccTLD row
         -- ccTLD row
         infoBox:tag('tr')
         infoBox:tag('tr')
             :tag('td'):wikitext('ccTLD'):done()
             :tag('th'):wikitext('ccTLD'):done()
             :tag('td'):wikitext(ccTLDText):done()
             :tag('td'):wikitext(ccTLDText):done()
             :done()
             :done()
Line 287: Line 309:
         -- ICANN region row
         -- ICANN region row
         infoBox:tag('tr')
         infoBox:tag('tr')
             :tag('td'):wikitext('ICANN region'):done()
             :tag('th'):wikitext('ICANN region'):done()
             :tag('td'):wikitext(regionText):done()
             :tag('td'):wikitext(regionText):done()
             :done()
             :done()
Line 295: Line 317:
         -- ISOC chapter row
         -- ISOC chapter row
         infoBox:tag('tr')
         infoBox:tag('tr')
             :tag('td'):wikitext('ISOC chapter'):done()
             :tag('th'):wikitext('ISOC chapter'):done()
             :tag('td'):wikitext(ISOCText):done()
             :tag('td'):wikitext(ISOCText):done()
             :done()
             :done()
          
          
         -- Youth IGF row
         infoBoxWrapper:wikitext(flagImageWikitext)
        infoBox:tag('tr')
            :tag('td'):wikitext('Youth IGF'):done()
            :tag('td'):wikitext(youthText):done()
            :done()
          
          
         return tostring(infoBox)
         return tostring(infoBoxWrapper)
     end
     end
}
}
Line 347: Line 365:
     feature = 'organizations',
     feature = 'organizations',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]] [[Has entity type::Organization]]', queryCountryName),
             string.format('[[Has country::%s]] [[Has entity type::Organization]]', args.has_country),
             limit    = 50
             limit    = 20
         }
         }
         local data = askCached('organizations:' .. queryCountryName, params)
         local data = askCached('organizations:' .. args.has_country, params)
         local htmlTable = renderTable(data, {'Organization'})
         -- Store the raw count for masonry layout
         local processedBrowseLink = generateProcessedBrowseLink(
        template._rawDataCounts = template._rawDataCounts or {}
            template,
         template._rawDataCounts.organizations = #data
            "Organization",
         -- Only render if we have data
            "Has_country",
         if #data == 0 then
            "Browse all organizations for %s",
            return ''
            queryCountryName
        end
         )
        return renderTable(data, {'Organizations'})
         return '<div class="icannwiki-hubs-container"><div class="icannwiki-hub-column">\n'
            .. '=== Organizations ===\n'
            .. htmlTable .. '\n' -- .. processedBrowseLink .. '\n'
            .. '</div>'
     end
     end
}
}
Line 373: Line 385:
     feature = 'people',
     feature = 'people',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]] [[Has entity type::Person]]',  
             string.format('[[Has country::%s]] [[Has entity type::Person]]',  
             queryCountryName),
             args.has_country),
             limit    = 20
             limit    = 20
         }
         }
         local data = askCached('people:' .. queryCountryName, params)
         local data = askCached('people:' .. args.has_country, params)
        local tableHtml = renderTable(data, {'Person'})
         -- Store the raw count for masonry layout
         -- local processedLink = generateProcessedBrowseLink(
         template._rawDataCounts = template._rawDataCounts or {}
         --    template,
         template._rawDataCounts.people = #data
         --    "Person",
         -- Only render if we have data
        --    "Has_country",
         if #data == 0 then
        --    "Browse all people for %s",
            return ''
         --     queryCountryName
        end
         -- )
        return renderTable(data, {'People'})
        local html = '<div class="icannwiki-hubs-container"><div class="icannwiki-hub-column">\n' ..
                    '=== People ===\n' ..
                    tableHtml .. '\n' ..
                    -- processedLink .. '\n' ..
                    '</div></div>'
        return html
     end,
     end,
}
}
Line 402: Line 406:
     feature = 'laws',
     feature = 'laws',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]]', queryCountryName),
             string.format('[[Has country::%s]] [[Has entity type::Norm]]', args.has_country),
            '[[Category:Laws]]',
             limit    = 20
            mainlabel = 'Law', sort = 'Has date', order = 'desc',
             limit    = 50
         }
         }
         local data = askCached('laws:' .. queryCountryName, params)
         local data = askCached('laws:' .. args.has_country, params)
        local tableHtml = renderTable(data, {'Law'})
         -- Store the raw count for masonry layout
         -- local processedLink = generateProcessedBrowseLink(
         template._rawDataCounts = template._rawDataCounts or {}
         --    template,
         template._rawDataCounts.laws = #data
         --    "Laws",
         -- Only render if we have data
         --     "Has_country",
         if #data == 0 then
         --    "Browse all laws and regulations for %s",
            return ''
        --    queryCountryName
         end
         -- )
         return renderTable(data, {'Laws and Regulations'})
         return '=== Laws and Regulations ===\n' .. tableHtml -- .. '\n' .. processedLink
     end,
     end,
}
}
Line 427: Line 426:
     feature = 'documents',
     feature = 'documents',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]]', queryCountryName),
             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    = 50
             limit    = 20
         }
         }
         local data = askCached('documents:' .. queryCountryName, params)
         return renderSection('documents:' .. args.has_country, params, {'Key Documents'})
        local tableHtml = renderTable(data, {'Document'})
        -- local processedLink = generateProcessedBrowseLink(
        --    template,
        --    "Document",
        --    "Has_country",
        --    "Browse all documents for %s",
        --    queryCountryName
        -- )
        return '=== Key Documents ===\n' .. tableHtml -- .. '\n' .. processedLink
     end,
     end,
}
}
Line 452: Line 440:
     feature = 'geoTlds',
     feature = 'geoTlds',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', queryCountryName),
             string.format('[[Has country::%s]] [[Has entity type::TLD]] [[Has TLD subtype::geoTLD]]', args.has_country),
             limit    = 50
             limit    = 20
         }
         }
         local data = askCached('geoTlds:' .. queryCountryName, params)
         local data = askCached('geoTlds:' .. args.has_country, params)
        local tableHtml = renderTable(data, {'GeoTLD'})
         -- Store the raw count for masonry layout
         -- local processedLink = generateProcessedBrowseLink(
         template._rawDataCounts = template._rawDataCounts or {}
         --    template,
         template._rawDataCounts.geoTlds = #data
         --    "TLD",
         -- Only render if we have data
         --     "Has_country",
         if #data == 0 then
         --    "Browse all TLDs for %s",
            return ''
        --    queryCountryName
         end
         -- )
         return renderTable(data, {'GeoTLDs'})
         return '=== GeoTLDs ===\n' .. tableHtml -- .. '\n' .. processedLink
     end,
     end,
}
}
Line 475: Line 460:
     feature = 'meetings',
     feature = 'meetings',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]]', queryCountryName),  
             string.format('[[Has country::%s]]', args.has_country),  
             '[[Has entity type::Event]]',  
             '[[Has entity type::Event]]',  
            mainlabel = 'Event',
             limit    = 20
             limit    = 50
         }
         }
       
         local data = askCached('events:' .. args.has_country, params)
        -- local processedLink = generateProcessedBrowseLink(
         -- Store the raw count for masonry layout
        --    template,
         template._rawDataCounts = template._rawDataCounts or {}
        --    "Event",
        template._rawDataCounts.meetings = #data
        --    "Has_country",
        -- Only render if we have data
        --    "Browse all events for %s",
         if #data == 0 then
        --    queryCountryName
             return ''
        -- )
       
         local data = askCached('events:' .. queryCountryName, params)
          
         if not data or #data == 0 then
            return '=== Internet Governance Events ===\n<p>No events found for ' .. displayCountry .. '.</p>\n' -- .. processedLink
         else
            local tableHtml = renderTable(data, {'Event'})
             return '=== Internet Governance Events ===\n' .. tableHtml -- .. '\n' .. processedLink
         end
         end
        return renderTable(data, {'Internet Governance Events'})
     end,
     end,
}
}
Line 508: Line 481:
     feature = 'nra',
     feature = 'nra',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', queryCountryName),
             string.format('[[Has country::%s]] [[Has entity type::Organization]] [[Has organization type::Government agency]]', args.has_country),
             limit    = 10
             limit    = 20
         }
         }
         local data = askCached('nra:' .. queryCountryName, params)
         local data = askCached('nra:' .. args.has_country, params)
         local tableHtml = renderTable(data, {'Authority'})
         -- Store the raw count for masonry layout
        return '=== National authorities ===\n' .. tableHtml
        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 526: Line 503:
     feature = 'resources',
     feature = 'resources',
     render = function(template, args)
     render = function(template, args)
        local displayCountry = (args.country or mw.title.getCurrentTitle().text or ""):gsub('_',' ')
        local queryCountryName = args.has_country
         local params = {
         local params = {
             string.format('[[Has country::%s]]', queryCountryName),
             string.format('[[Has country::%s]]', args.has_country),
             '[[Category:Resource]]',
             '[[Category:Resource]]',
             mainlabel = 'Resource',
             mainlabel = 'Resource',
             limit    = 10
             limit    = 20
         }
         }
         local data = askCached('resources:' .. queryCountryName, params)
         return renderSection('resources:' .. args.has_country, params, {'Resources'})
        local tableHtml = renderTable(data, {'Resource'})
        return '== Resources ==\n' .. tableHtml
     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 544: 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
}
}