Jump to content

Module:SocialMedia: Difference between revisions

Created page with "-- Reusable, single-row or multi-row social media footer that can be called from any other module or template. local sf = {} -- 1) Define the social platforms -- param = the name of the parameter -- icon = wiki file for the icon -- prefix = URL prefix -- label = optional for referencing or debugging local socialPlatforms = { { param = "twitter", icon = "File:TwitterIcon.png", -- or "File:XIcon.png" prefix = "https://x..."
 
// via Wikitext Extension for VSCode
 
(58 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Reusable, single-row or multi-row social media footer that can be called from any other module or template.
-- Module:SocialMedia
-- Reusable social media component meant to be called from other modules
 
-- ###############################################################
-- Currently supported platforms are:
-- (UPDATE THE LIST BETWEEN THE HOOK EMOJIS AS PLATFORMS GET ADDED OR REMOVED)
-- 🪝Facebook, Github, Instagram, LinkedIn, Telegram, Threads, TikTok, X/Twitter, Youtube🪝
-- ###############################################################


local sf = {}
local sf = {}
local TemplateHelpers = require('Module:TemplateHelpers')


-- 1) Define the social platforms
-- This section defines the social platforms; can be added or deleted without consequence
--   param   = the name of the parameter
-- param = handle for the platform; for "x", we also accept "twitter" for legacy purposes
--   icon   = wiki file for the icon
-- icon = wiki file for the icon
--   prefix = URL prefix
-- prefix = URL prefix
--   label   = optional for referencing or debugging
-- label = used for alt text/screen readers
local socialPlatforms = {
local socialPlatforms = {
    {
        param  = "twitter",
        icon  = "File:TwitterIcon.png",  -- or "File:XIcon.png"
        prefix = "https://x.com/",
        label  = "X (Twitter)"
    },
     {
     {
         param  = "facebook",
         param  = "facebook",
         icon  = "File:FacebookIcon.png",
         icon  = "File:SocialFacebookIcon.svg",
         prefix = "https://www.facebook.com/",
         prefix = "https://www.facebook.com/",
         label  = "Facebook"
         label  = "Facebook"
Line 23: Line 25:
     {
     {
         param  = "instagram",
         param  = "instagram",
         icon  = "File:InstagramIcon.png",
         icon  = "File:SocialInstagramIcon.svg",
         prefix = "https://www.instagram.com/",
         prefix = "https://www.instagram.com/",
         label  = "Instagram"
         label  = "Instagram"
    },
    {
        param  = "linkedin",
        icon  = "File:SocialLinkedInIcon.svg",
        prefix = "https://www.linkedin.com/in/",
        label  = "LinkedIn"
    },
    {
        param  = "telegram",
        icon  = "File:SocialTelegramIcon.svg",
        prefix = "https://t.me/",
        label  = "Telegram"
     },
     },
     {
     {
         param  = "threads",
         param  = "threads",
         icon  = "File:ThreadsIcon.png",
         icon  = "File:SocialThreadsIcon.svg",
         prefix = "https://www.threads.net/",
         prefix = "https://www.threads.net/@",
         label  = "Threads"
         label  = "Threads"
     },
     },
     {
     {
         param  = "linkedin",
         param  = "tiktok",
         icon  = "File:LinkedInIcon.png",
         icon  = "File:SocialTikTokIcon.svg",
         prefix = "https://www.linkedin.com/",
         prefix = "https://www.tiktok.com/@",
         label  = "LinkedIn"
         label  = "TikTok"
    },
    {
        param  = { "x", "twitter" },
        icon  = "File:SocialXIcon.svg",
        prefix = "https://x.com/",
        label  = "X (Twitter)"
     },
     },
     {
     {
         param  = "youtube",
         param  = "youtube",
         icon  = "File:YouTubeIcon.png",
         icon  = "File:SocialYouTubeIcon.svg",
         prefix = "https://www.youtube.com/",
         prefix = "https://www.youtube.com/@",
         label  = "YouTube"
         label  = "YouTube"
     },
     },
     {
     {
         param  = "mastodon",
         param  = "github",
         icon  = "File:MastodonIcon.png",
         icon  = "File:SocialGithub.svg",
        prefix = "",
         prefix = "https://github.com/",
        label  = "Mastodon"
         label  = "Github"
    },
    {
        param  = "tiktok",
        icon  = "File:TikTokIcon.png",
         prefix = "https://www.tiktok.com/",
         label  = "TikTok"
     },
     },
}
}


-- 2) Helper function to build a clickable icon link
-- Helper function to check if text is a full URL
--   Using wikitext syntax: [[File:Something.png|16px|link=URL]]
local function isFullUrl(text)
local function buildIconLink(iconFile, url)
    return text and text:match("^https?://") ~= nil
     return string.format("[[%s|16px|link=%s]]", iconFile, url)
end
 
-- Platform-specific normalizers table
local normalizers = {
    linkedin = function(handle)
        -- If it's not a URL, return as is
        if not isFullUrl(handle) then return handle end
       
        -- Extract the path from the URL (case insensitive for linkedin.com)
        local path = handle:lower():match("linkedin%.com/(.+)")
        if not path then return handle end
       
        -- Handle /pub/ format
        if path:match("^pub/") then
            -- Extract the name part (first segment after /pub/)
            local name = path:match("^pub/([^/]+)")
            if name then return name end
        end
       
        -- Handle /in/ format - just extract the username
        if path:match("^in/") then
            return path:match("^in/([^/]+)")
        end
       
        -- If we can't parse it, return the original
        return handle
    end
    -- Add other platform normalizers as needed
}
 
-- Helper function to normalize handles based on platform
local function normalizeHandle(platform, handle)
     -- Skip normalization if handle is nil or empty
    if not handle or handle == "" then return handle end
   
    -- Get the platform param (use first one if it's a table)
    local platformParam = platform.param
    if type(platformParam) == "table" then
        platformParam = platformParam[1]
    end
   
    -- If platform has a specific normalizer, use it
    if normalizers[platformParam] then
        return normalizers[platformParam](handle)
    end
   
    -- Default behavior: if it's a full URL, try to extract the handle
    if isFullUrl(handle) then
        -- Try to extract the handle from the URL based on the platform's prefix
        local prefix = platform.prefix:gsub("([%%%-%.])", "%%%1") -- Escape special pattern chars
        return handle:match(prefix .. "(.+)") or handle
    end
   
    -- If not a URL, return as is
    return handle
end
end


-- 3) The main render function, called from another module or via #invoke
-- (Non-generalizable) Helper to find the first non-empty user handle from a param or table of params
-- Takes a table of arguments (handles), returns wiki markup with social icons.
local function getUserHandle(args, param)
    local key, handle
    if type(param) == "string" then
        key, handle = TemplateHelpers.getFieldValue(args, { key = param })
    else
        key, handle = TemplateHelpers.getFieldValue(args, { keys = param })
    end
    if handle and handle ~= "" then
        return mw.text.trim(handle)
    end
    return nil
end
 
-- Build a clickable icon link (24px) with alt text for accessibility: [[File:Name.svg|24px|class=...|alt=...|link=URL]]
local function buildIconLink(iconFile, altText, url)
    -- Add the reusable filter class to the image attributes
    return string.format("[[%s|24px|alt=%s|link=%s|class=filter-icon-green]]", iconFile, altText, url)
end
 
-- Build a single table row with colspan=2, containing a flex container for icons
local function buildSocialRow(icons)
    local iconMarkup = table.concat({
        '<div class="social-icons link-open-external">',
            table.concat(icons, ""),
        '</div>'
    }, "\n")
   
    return table.concat({
        "|-",
        '| colspan="2" |',
        iconMarkup
    }, "\n")
end
 
-- Main render function, takes a table of arguments (handles), returns wiki markup with social icons
function sf.render(args)
function sf.render(args)
    -- Early return if args is empty or nil
    if not args or next(args) == nil then
        return ""
    end
   
    -- Pre-allocate icons table based on maximum possible size (number of platforms)
     local icons = {}
     local icons = {}
    icons[#socialPlatforms] = nil  -- Pre-allocate table to avoid reallocation
    local iconCount = 0


     for _, platform in ipairs(socialPlatforms) do
     for _, platform in ipairs(socialPlatforms) do
         local handle = args[platform.param]
         local handle = getUserHandle(args, platform.param)
         if handle and handle ~= "" then
         if handle and handle ~= "" then
            -- Normalize the handle based on platform
            local normalizedHandle = normalizeHandle(platform, handle)
           
            -- Check for special FULLURL: prefix from Python transformation
             local url
             local url
 
             if handle:match("^FULLURL:") then
             -- Special-case Mastodon, as it can vary. Example approach:
                -- Extract the URL part after the FULLURL: prefix
            if platform.param == "mastodon" then
                url = handle:gsub("^FULLURL:", "")
                if handle:match("^https?://") then
            -- If the handle is already a full URL, use it directly
                    -- user typed a full URL
            elseif isFullUrl(handle) and not isFullUrl(normalizedHandle) then
                    url = handle
                -- We have a full URL that was successfully normalized to a handle
                else
                url = platform.prefix .. normalizedHandle
                    -- parse e.g. "icann@mastodon.social"
            elseif isFullUrl(handle) then
                    local domain = handle:match("@([^@]+)$") or "mastodon.social"
                -- We have a full URL that couldn't be normalized, use it directly
                    local user  = handle:match("^(.-)@") or handle
                 url = handle
                    url = "https://" .. domain .. "/@" .. user
                 end
             else
             else
                 -- For everything else, prefix + handle
                 -- We have a regular handle, use the standard approach
                 url = platform.prefix .. handle
                 url = platform.prefix .. handle
             end
             end
 
              
             -- Build an icon link
             local iconLink = buildIconLink(platform.icon, platform.label, url)
             local iconLink = buildIconLink(platform.icon, url)
             iconCount = iconCount + 1
             table.insert(icons, iconLink)
            icons[iconCount] = iconLink  -- Direct index assignment is faster than table.insert
         end
         end
     end
     end


     -- If no handles were provided, return nothing (empty string).
     -- If no handles were provided, return nothing
     if #icons == 0 then
     if #icons == 0 then
         return ""
         return ""
     end
     end


     -- Otherwise, return a table row or any layout you want.
     -- Return a row with a flex container for icons
     -- Example: single row labeled "Social Media"
     return buildSocialRow(icons)
    local iconString = table.concat(icons, " &nbsp; ")
    local row = table.concat({
        "|-",
        "| '''Social Media''':",  -- Label on the left
        "| " .. iconString        -- Icons on the right
    }, "\n")
 
    return row
end
end


return sf
return sf

Latest revision as of 00:58, 3 September 2025

Documentation for this module may be created at Module:SocialMedia/doc

-- Module:SocialMedia
-- Reusable social media component meant to be called from other modules

-- ###############################################################
-- Currently supported platforms are:
-- (UPDATE THE LIST BETWEEN THE HOOK EMOJIS AS PLATFORMS GET ADDED OR REMOVED)
-- 🪝Facebook, Github, Instagram, LinkedIn, Telegram, Threads, TikTok, X/Twitter, Youtube🪝
-- ###############################################################

local sf = {}
local TemplateHelpers = require('Module:TemplateHelpers')

-- This section defines the social platforms; can be added or deleted without consequence
-- param = handle for the platform; for "x", we also accept "twitter" for legacy purposes
-- icon  = wiki file for the icon
-- prefix = URL prefix
-- label  = used for alt text/screen readers
local socialPlatforms = {
    {
        param  = "facebook",
        icon   = "File:SocialFacebookIcon.svg",
        prefix = "https://www.facebook.com/",
        label  = "Facebook"
    },
    {
        param  = "instagram",
        icon   = "File:SocialInstagramIcon.svg",
        prefix = "https://www.instagram.com/",
        label  = "Instagram"
    },
    {
        param  = "linkedin",
        icon   = "File:SocialLinkedInIcon.svg",
        prefix = "https://www.linkedin.com/in/",
        label  = "LinkedIn"
    },
    {
        param  = "telegram",
        icon   = "File:SocialTelegramIcon.svg",
        prefix = "https://t.me/",
        label  = "Telegram"
    },
    {
        param  = "threads",
        icon   = "File:SocialThreadsIcon.svg",
        prefix = "https://www.threads.net/@",
        label  = "Threads"
    },
    {
        param  = "tiktok",
        icon   = "File:SocialTikTokIcon.svg",
        prefix = "https://www.tiktok.com/@",
        label  = "TikTok"
    },
    {
        param  = { "x", "twitter" },
        icon   = "File:SocialXIcon.svg",
        prefix = "https://x.com/",
        label  = "X (Twitter)"
    },
    {
        param  = "youtube",
        icon   = "File:SocialYouTubeIcon.svg",
        prefix = "https://www.youtube.com/@",
        label  = "YouTube"
    },
    {
        param  = "github",
        icon   = "File:SocialGithub.svg",
        prefix = "https://github.com/",
        label  = "Github"
    },
}

-- Helper function to check if text is a full URL
local function isFullUrl(text)
    return text and text:match("^https?://") ~= nil
end

-- Platform-specific normalizers table
local normalizers = {
    linkedin = function(handle)
        -- If it's not a URL, return as is
        if not isFullUrl(handle) then return handle end
        
        -- Extract the path from the URL (case insensitive for linkedin.com)
        local path = handle:lower():match("linkedin%.com/(.+)")
        if not path then return handle end
        
        -- Handle /pub/ format
        if path:match("^pub/") then
            -- Extract the name part (first segment after /pub/)
            local name = path:match("^pub/([^/]+)")
            if name then return name end
        end
        
        -- Handle /in/ format - just extract the username
        if path:match("^in/") then
            return path:match("^in/([^/]+)")
        end
        
        -- If we can't parse it, return the original
        return handle
    end
    -- Add other platform normalizers as needed
}

-- Helper function to normalize handles based on platform
local function normalizeHandle(platform, handle)
    -- Skip normalization if handle is nil or empty
    if not handle or handle == "" then return handle end
    
    -- Get the platform param (use first one if it's a table)
    local platformParam = platform.param
    if type(platformParam) == "table" then
        platformParam = platformParam[1]
    end
    
    -- If platform has a specific normalizer, use it
    if normalizers[platformParam] then
        return normalizers[platformParam](handle)
    end
    
    -- Default behavior: if it's a full URL, try to extract the handle
    if isFullUrl(handle) then
        -- Try to extract the handle from the URL based on the platform's prefix
        local prefix = platform.prefix:gsub("([%%%-%.])", "%%%1") -- Escape special pattern chars
        return handle:match(prefix .. "(.+)") or handle
    end
    
    -- If not a URL, return as is
    return handle
end

-- (Non-generalizable) Helper to find the first non-empty user handle from a param or table of params
local function getUserHandle(args, param)
    local key, handle
    if type(param) == "string" then
        key, handle = TemplateHelpers.getFieldValue(args, { key = param })
    else
        key, handle = TemplateHelpers.getFieldValue(args, { keys = param })
    end
    if handle and handle ~= "" then
        return mw.text.trim(handle)
    end
    return nil
end

-- Build a clickable icon link (24px) with alt text for accessibility: [[File:Name.svg|24px|class=...|alt=...|link=URL]]
local function buildIconLink(iconFile, altText, url)
    -- Add the reusable filter class to the image attributes
    return string.format("[[%s|24px|alt=%s|link=%s|class=filter-icon-green]]", iconFile, altText, url)
end

-- Build a single table row with colspan=2, containing a flex container for icons
local function buildSocialRow(icons)
    local iconMarkup = table.concat({
        '<div class="social-icons link-open-external">',
            table.concat(icons, ""),
        '</div>'
    }, "\n")
    
    return table.concat({
        "|-",
        '| colspan="2" |',
        iconMarkup
    }, "\n")
end

-- Main render function, takes a table of arguments (handles), returns wiki markup with social icons
function sf.render(args)
    -- Early return if args is empty or nil
    if not args or next(args) == nil then
        return ""
    end
    
    -- Pre-allocate icons table based on maximum possible size (number of platforms)
    local icons = {}
    icons[#socialPlatforms] = nil  -- Pre-allocate table to avoid reallocation
    local iconCount = 0

    for _, platform in ipairs(socialPlatforms) do
        local handle = getUserHandle(args, platform.param)
        if handle and handle ~= "" then
            -- Normalize the handle based on platform
            local normalizedHandle = normalizeHandle(platform, handle)
            
            -- Check for special FULLURL: prefix from Python transformation
            local url
            if handle:match("^FULLURL:") then
                -- Extract the URL part after the FULLURL: prefix
                url = handle:gsub("^FULLURL:", "")
            -- If the handle is already a full URL, use it directly
            elseif isFullUrl(handle) and not isFullUrl(normalizedHandle) then
                -- We have a full URL that was successfully normalized to a handle
                url = platform.prefix .. normalizedHandle
            elseif isFullUrl(handle) then
                -- We have a full URL that couldn't be normalized, use it directly
                url = handle
            else
                -- We have a regular handle, use the standard approach
                url = platform.prefix .. handle
            end
            
            local iconLink = buildIconLink(platform.icon, platform.label, url)
            iconCount = iconCount + 1
            icons[iconCount] = iconLink  -- Direct index assignment is faster than table.insert
        end
    end

    -- If no handles were provided, return nothing
    if #icons == 0 then
        return ""
    end

    -- Return a row with a flex container for icons
    return buildSocialRow(icons)
end

return sf